xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (revision c2067c1f471ac54312cb5e1e0efd4ea5fd21cc79)
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 "llvm/ADT/Sequence.h"
25 #include <functional>
26 #include <optional>
27 
28 using namespace clang;
29 using namespace ento;
30 using namespace std::placeholders;
31 
32 //===----------------------------------------------------------------------===//
33 // Definition of state data structures.
34 //===----------------------------------------------------------------------===//
35 
36 namespace {
37 
38 struct FnDescription;
39 
40 /// State of the stream error flags.
41 /// Sometimes it is not known to the checker what error flags are set.
42 /// This is indicated by setting more than one flag to true.
43 /// This is an optimization to avoid state splits.
44 /// A stream can either be in FEOF or FERROR but not both at the same time.
45 /// Multiple flags are set to handle the corresponding states together.
46 struct StreamErrorState {
47   /// The stream can be in state where none of the error flags set.
48   bool NoError = true;
49   /// The stream can be in state where the EOF indicator is set.
50   bool FEof = false;
51   /// The stream can be in state where the error indicator is set.
52   bool FError = false;
53 
54   bool isNoError() const { return NoError && !FEof && !FError; }
55   bool isFEof() const { return !NoError && FEof && !FError; }
56   bool isFError() const { return !NoError && !FEof && FError; }
57 
58   bool operator==(const StreamErrorState &ES) const {
59     return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError;
60   }
61 
62   bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); }
63 
64   StreamErrorState operator|(const StreamErrorState &E) const {
65     return {NoError || E.NoError, FEof || E.FEof, FError || E.FError};
66   }
67 
68   StreamErrorState operator&(const StreamErrorState &E) const {
69     return {NoError && E.NoError, FEof && E.FEof, FError && E.FError};
70   }
71 
72   StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; }
73 
74   /// Returns if the StreamErrorState is a valid object.
75   operator bool() const { return NoError || FEof || FError; }
76 
77   void Profile(llvm::FoldingSetNodeID &ID) const {
78     ID.AddBoolean(NoError);
79     ID.AddBoolean(FEof);
80     ID.AddBoolean(FError);
81   }
82 };
83 
84 const StreamErrorState ErrorNone{true, false, false};
85 const StreamErrorState ErrorFEof{false, true, false};
86 const StreamErrorState ErrorFError{false, false, true};
87 
88 /// Full state information about a stream pointer.
89 struct StreamState {
90   /// The last file operation called in the stream.
91   /// Can be nullptr.
92   const FnDescription *LastOperation;
93 
94   /// State of a stream symbol.
95   enum KindTy {
96     Opened, /// Stream is opened.
97     Closed, /// Closed stream (an invalid stream pointer after it was closed).
98     OpenFailed /// The last open operation has failed.
99   } State;
100 
101   /// State of the error flags.
102   /// Ignored in non-opened stream state but must be NoError.
103   StreamErrorState const ErrorState;
104 
105   /// Indicate if the file has an "indeterminate file position indicator".
106   /// This can be set at a failing read or write or seek operation.
107   /// If it is set no more read or write is allowed.
108   /// This value is not dependent on the stream error flags:
109   /// The error flag may be cleared with `clearerr` but the file position
110   /// remains still indeterminate.
111   /// This value applies to all error states in ErrorState except FEOF.
112   /// An EOF+indeterminate state is the same as EOF state.
113   bool const FilePositionIndeterminate = false;
114 
115   StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES,
116               bool IsFilePositionIndeterminate)
117       : LastOperation(L), State(S), ErrorState(ES),
118         FilePositionIndeterminate(IsFilePositionIndeterminate) {
119     assert((!ES.isFEof() || !IsFilePositionIndeterminate) &&
120            "FilePositionIndeterminate should be false in FEof case.");
121     assert((State == Opened || ErrorState.isNoError()) &&
122            "ErrorState should be None in non-opened stream state.");
123   }
124 
125   bool isOpened() const { return State == Opened; }
126   bool isClosed() const { return State == Closed; }
127   bool isOpenFailed() const { return State == OpenFailed; }
128 
129   bool operator==(const StreamState &X) const {
130     // In not opened state error state should always NoError, so comparison
131     // here is no problem.
132     return LastOperation == X.LastOperation && State == X.State &&
133            ErrorState == X.ErrorState &&
134            FilePositionIndeterminate == X.FilePositionIndeterminate;
135   }
136 
137   static StreamState getOpened(const FnDescription *L,
138                                const StreamErrorState &ES = ErrorNone,
139                                bool IsFilePositionIndeterminate = false) {
140     return StreamState{L, Opened, ES, IsFilePositionIndeterminate};
141   }
142   static StreamState getClosed(const FnDescription *L) {
143     return StreamState{L, Closed, {}, false};
144   }
145   static StreamState getOpenFailed(const FnDescription *L) {
146     return StreamState{L, OpenFailed, {}, false};
147   }
148 
149   void Profile(llvm::FoldingSetNodeID &ID) const {
150     ID.AddPointer(LastOperation);
151     ID.AddInteger(State);
152     ErrorState.Profile(ID);
153     ID.AddBoolean(FilePositionIndeterminate);
154   }
155 };
156 
157 } // namespace
158 
159 // This map holds the state of a stream.
160 // The stream is identified with a SymbolRef that is created when a stream
161 // opening function is modeled by the checker.
162 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
163 
164 //===----------------------------------------------------------------------===//
165 // StreamChecker class and utility functions.
166 //===----------------------------------------------------------------------===//
167 
168 namespace {
169 
170 class StreamChecker;
171 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
172                                    const CallEvent &, CheckerContext &)>;
173 
174 using ArgNoTy = unsigned int;
175 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();
176 
177 const char *FeofNote = "Assuming stream reaches end-of-file here";
178 const char *FerrorNote = "Assuming this stream operation fails";
179 
180 struct FnDescription {
181   FnCheck PreFn;
182   FnCheck EvalFn;
183   ArgNoTy StreamArgNo;
184 };
185 
186 /// Get the value of the stream argument out of the passed call event.
187 /// The call should contain a function that is described by Desc.
188 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) {
189   assert(Desc && Desc->StreamArgNo != ArgNone &&
190          "Try to get a non-existing stream argument.");
191   return Call.getArgSVal(Desc->StreamArgNo);
192 }
193 
194 /// Create a conjured symbol return value for a call expression.
195 DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) {
196   assert(CE && "Expecting a call expression.");
197 
198   const LocationContext *LCtx = C.getLocationContext();
199   return C.getSValBuilder()
200       .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
201       .castAs<DefinedSVal>();
202 }
203 
204 ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C,
205                                   const CallExpr *CE) {
206   DefinedSVal RetVal = makeRetVal(C, CE);
207   State = State->BindExpr(CE, C.getLocationContext(), RetVal);
208   State = State->assume(RetVal, true);
209   assert(State && "Assumption on new value should not fail.");
210   return State;
211 }
212 
213 ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State,
214                         CheckerContext &C, const CallExpr *CE) {
215   State = State->BindExpr(CE, C.getLocationContext(),
216                           C.getSValBuilder().makeIntVal(Value, CE->getType()));
217   return State;
218 }
219 
220 inline void assertStreamStateOpened(const StreamState *SS) {
221   assert(SS->isOpened() && "Stream is expected to be opened");
222 }
223 
224 class StreamChecker : public Checker<check::PreCall, eval::Call,
225                                      check::DeadSymbols, check::PointerEscape> {
226   BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"};
227   BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"};
228   BugType BT_UseAfterOpenFailed{this, "Invalid stream",
229                                 "Stream handling error"};
230   BugType BT_IndeterminatePosition{this, "Invalid stream state",
231                                    "Stream handling error"};
232   BugType BT_IllegalWhence{this, "Illegal whence argument",
233                            "Stream handling error"};
234   BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};
235   BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
236                           /*SuppressOnSink =*/true};
237 
238 public:
239   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
240   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
241   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
242   ProgramStateRef checkPointerEscape(ProgramStateRef State,
243                                      const InvalidatedSymbols &Escaped,
244                                      const CallEvent *Call,
245                                      PointerEscapeKind Kind) const;
246 
247   const BugType *getBT_StreamEof() const { return &BT_StreamEof; }
248   const BugType *getBT_IndeterminatePosition() const {
249     return &BT_IndeterminatePosition;
250   }
251 
252   const NoteTag *constructSetEofNoteTag(CheckerContext &C,
253                                         SymbolRef StreamSym) const {
254     return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
255       if (!BR.isInteresting(StreamSym) ||
256           &BR.getBugType() != this->getBT_StreamEof())
257         return "";
258 
259       BR.markNotInteresting(StreamSym);
260 
261       return FeofNote;
262     });
263   }
264 
265   const NoteTag *constructSetErrorNoteTag(CheckerContext &C,
266                                           SymbolRef StreamSym) const {
267     return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
268       if (!BR.isInteresting(StreamSym) ||
269           &BR.getBugType() != this->getBT_IndeterminatePosition())
270         return "";
271 
272       BR.markNotInteresting(StreamSym);
273 
274       return FerrorNote;
275     });
276   }
277 
278   const NoteTag *constructSetEofOrErrorNoteTag(CheckerContext &C,
279                                                SymbolRef StreamSym) const {
280     return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
281       if (!BR.isInteresting(StreamSym))
282         return "";
283 
284       if (&BR.getBugType() == this->getBT_StreamEof()) {
285         BR.markNotInteresting(StreamSym);
286         return FeofNote;
287       }
288       if (&BR.getBugType() == this->getBT_IndeterminatePosition()) {
289         BR.markNotInteresting(StreamSym);
290         return FerrorNote;
291       }
292 
293       return "";
294     });
295   }
296 
297   /// If true, evaluate special testing stream functions.
298   bool TestMode = false;
299 
300   /// If true, generate failure branches for cases that are often not checked.
301   bool PedanticMode = false;
302 
303 private:
304   CallDescriptionMap<FnDescription> FnDescriptions = {
305       {{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
306       {{{"fdopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
307       {{{"freopen"}, 3},
308        {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
309       {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
310       {{{"fclose"}, 1},
311        {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
312       {{{"fread"}, 4},
313        {&StreamChecker::preRead,
314         std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},
315       {{{"fwrite"}, 4},
316        {&StreamChecker::preWrite,
317         std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},
318       {{{"fgetc"}, 1},
319        {&StreamChecker::preRead,
320         std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
321       {{{"fgets"}, 3},
322        {&StreamChecker::preRead,
323         std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},
324       {{{"getc"}, 1},
325        {&StreamChecker::preRead,
326         std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
327       {{{"fputc"}, 2},
328        {&StreamChecker::preWrite,
329         std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
330       {{{"fputs"}, 2},
331        {&StreamChecker::preWrite,
332         std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
333       {{{"putc"}, 2},
334        {&StreamChecker::preWrite,
335         std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
336       {{{"fprintf"}},
337        {&StreamChecker::preWrite,
338         std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
339       {{{"vfprintf"}, 3},
340        {&StreamChecker::preWrite,
341         std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
342       {{{"fscanf"}},
343        {&StreamChecker::preRead,
344         std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
345       {{{"vfscanf"}, 3},
346        {&StreamChecker::preRead,
347         std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
348       {{{"ungetc"}, 2},
349        {&StreamChecker::preWrite,
350         std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
351       {{{"getdelim"}, 4},
352        {&StreamChecker::preRead,
353         std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
354       {{{"getline"}, 3},
355        {&StreamChecker::preRead,
356         std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
357       {{{"fseek"}, 3},
358        {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
359       {{{"fseeko"}, 3},
360        {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
361       {{{"ftell"}, 1},
362        {&StreamChecker::preWrite, &StreamChecker::evalFtell, 0}},
363       {{{"ftello"}, 1},
364        {&StreamChecker::preWrite, &StreamChecker::evalFtell, 0}},
365       {{{"fflush"}, 1},
366        {&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}},
367       {{{"rewind"}, 1},
368        {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
369       {{{"fgetpos"}, 2},
370        {&StreamChecker::preWrite, &StreamChecker::evalFgetpos, 0}},
371       {{{"fsetpos"}, 2},
372        {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},
373       {{{"clearerr"}, 1},
374        {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
375       {{{"feof"}, 1},
376        {&StreamChecker::preDefault,
377         std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof),
378         0}},
379       {{{"ferror"}, 1},
380        {&StreamChecker::preDefault,
381         std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError),
382         0}},
383       {{{"fileno"}, 1},
384        {&StreamChecker::preDefault, &StreamChecker::evalFileno, 0}},
385   };
386 
387   CallDescriptionMap<FnDescription> FnTestDescriptions = {
388       {{{"StreamTesterChecker_make_feof_stream"}, 1},
389        {nullptr,
390         std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof,
391                   false),
392         0}},
393       {{{"StreamTesterChecker_make_ferror_stream"}, 1},
394        {nullptr,
395         std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
396                   ErrorFError, false),
397         0}},
398       {{{"StreamTesterChecker_make_ferror_indeterminate_stream"}, 1},
399        {nullptr,
400         std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
401                   ErrorFError, true),
402         0}},
403   };
404 
405   /// Expanded value of EOF, empty before initialization.
406   mutable std::optional<int> EofVal;
407   /// Expanded value of SEEK_SET, 0 if not found.
408   mutable int SeekSetVal = 0;
409   /// Expanded value of SEEK_CUR, 1 if not found.
410   mutable int SeekCurVal = 1;
411   /// Expanded value of SEEK_END, 2 if not found.
412   mutable int SeekEndVal = 2;
413   /// The built-in va_list type is platform-specific
414   mutable QualType VaListType;
415 
416   void evalFopen(const FnDescription *Desc, const CallEvent &Call,
417                  CheckerContext &C) const;
418 
419   void preFreopen(const FnDescription *Desc, const CallEvent &Call,
420                   CheckerContext &C) const;
421   void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
422                    CheckerContext &C) const;
423 
424   void evalFclose(const FnDescription *Desc, const CallEvent &Call,
425                   CheckerContext &C) const;
426 
427   void preRead(const FnDescription *Desc, const CallEvent &Call,
428                CheckerContext &C) const;
429 
430   void preWrite(const FnDescription *Desc, const CallEvent &Call,
431                 CheckerContext &C) const;
432 
433   void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
434                        CheckerContext &C, bool IsFread) const;
435 
436   void evalFgetx(const FnDescription *Desc, const CallEvent &Call,
437                  CheckerContext &C, bool SingleChar) const;
438 
439   void evalFputx(const FnDescription *Desc, const CallEvent &Call,
440                  CheckerContext &C, bool IsSingleChar) const;
441 
442   void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
443                    CheckerContext &C) const;
444 
445   void evalFscanf(const FnDescription *Desc, const CallEvent &Call,
446                   CheckerContext &C) const;
447 
448   void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
449                   CheckerContext &C) const;
450 
451   void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
452                     CheckerContext &C) const;
453 
454   void preFseek(const FnDescription *Desc, const CallEvent &Call,
455                 CheckerContext &C) const;
456   void evalFseek(const FnDescription *Desc, const CallEvent &Call,
457                  CheckerContext &C) const;
458 
459   void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,
460                    CheckerContext &C) const;
461 
462   void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,
463                    CheckerContext &C) const;
464 
465   void evalFtell(const FnDescription *Desc, const CallEvent &Call,
466                  CheckerContext &C) const;
467 
468   void evalRewind(const FnDescription *Desc, const CallEvent &Call,
469                   CheckerContext &C) const;
470 
471   void preDefault(const FnDescription *Desc, const CallEvent &Call,
472                   CheckerContext &C) const;
473 
474   void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
475                     CheckerContext &C) const;
476 
477   void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
478                       CheckerContext &C,
479                       const StreamErrorState &ErrorKind) const;
480 
481   void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
482                          CheckerContext &C, const StreamErrorState &ErrorKind,
483                          bool Indeterminate) const;
484 
485   void preFflush(const FnDescription *Desc, const CallEvent &Call,
486                  CheckerContext &C) const;
487 
488   void evalFflush(const FnDescription *Desc, const CallEvent &Call,
489                   CheckerContext &C) const;
490 
491   void evalFileno(const FnDescription *Desc, const CallEvent &Call,
492                   CheckerContext &C) const;
493 
494   /// Check that the stream (in StreamVal) is not NULL.
495   /// If it can only be NULL a fatal error is emitted and nullptr returned.
496   /// Otherwise the return value is a new state where the stream is constrained
497   /// to be non-null.
498   ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
499                                       CheckerContext &C,
500                                       ProgramStateRef State) const;
501 
502   /// Check that the stream is the opened state.
503   /// If the stream is known to be not opened an error is generated
504   /// and nullptr returned, otherwise the original state is returned.
505   ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
506                                      ProgramStateRef State) const;
507 
508   /// Check that the stream has not an invalid ("indeterminate") file position,
509   /// generate warning for it.
510   /// (EOF is not an invalid position.)
511   /// The returned state can be nullptr if a fatal error was generated.
512   /// It can return non-null state if the stream has not an invalid position or
513   /// there is execution path with non-invalid position.
514   ProgramStateRef
515   ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C,
516                                     ProgramStateRef State) const;
517 
518   /// Check the legality of the 'whence' argument of 'fseek'.
519   /// Generate error and return nullptr if it is found to be illegal.
520   /// Otherwise returns the state.
521   /// (State is not changed here because the "whence" value is already known.)
522   ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
523                                            ProgramStateRef State) const;
524 
525   /// Generate warning about stream in EOF state.
526   /// There will be always a state transition into the passed State,
527   /// by the new non-fatal error node or (if failed) a normal transition,
528   /// to ensure uniform handling.
529   void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
530                          ProgramStateRef State) const;
531 
532   /// Emit resource leak warnings for the given symbols.
533   /// Createn a non-fatal error node for these, and returns it (if any warnings
534   /// were generated). Return value is non-null.
535   ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
536                             CheckerContext &C, ExplodedNode *Pred) const;
537 
538   /// Find the description data of the function called by a call event.
539   /// Returns nullptr if no function is recognized.
540   const FnDescription *lookupFn(const CallEvent &Call) const {
541     // Recognize "global C functions" with only integral or pointer arguments
542     // (and matching name) as stream functions.
543     if (!Call.isGlobalCFunction())
544       return nullptr;
545     for (auto *P : Call.parameters()) {
546       QualType T = P->getType();
547       if (!T->isIntegralOrEnumerationType() && !T->isPointerType() &&
548           T.getCanonicalType() != VaListType)
549         return nullptr;
550     }
551 
552     return FnDescriptions.lookup(Call);
553   }
554 
555   /// Generate a message for BugReporterVisitor if the stored symbol is
556   /// marked as interesting by the actual bug report.
557   const NoteTag *constructLeakNoteTag(CheckerContext &C, SymbolRef StreamSym,
558                                       const std::string &Message) const {
559     return C.getNoteTag([this, StreamSym,
560                          Message](PathSensitiveBugReport &BR) -> std::string {
561       if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak)
562         return Message;
563       return "";
564     });
565   }
566 
567   void initMacroValues(CheckerContext &C) const {
568     if (EofVal)
569       return;
570 
571     if (const std::optional<int> OptInt =
572             tryExpandAsInteger("EOF", C.getPreprocessor()))
573       EofVal = *OptInt;
574     else
575       EofVal = -1;
576     if (const std::optional<int> OptInt =
577             tryExpandAsInteger("SEEK_SET", C.getPreprocessor()))
578       SeekSetVal = *OptInt;
579     if (const std::optional<int> OptInt =
580             tryExpandAsInteger("SEEK_END", C.getPreprocessor()))
581       SeekEndVal = *OptInt;
582     if (const std::optional<int> OptInt =
583             tryExpandAsInteger("SEEK_CUR", C.getPreprocessor()))
584       SeekCurVal = *OptInt;
585   }
586 
587   void initVaListType(CheckerContext &C) const {
588     VaListType = C.getASTContext().getBuiltinVaListType().getCanonicalType();
589   }
590 
591   /// Searches for the ExplodedNode where the file descriptor was acquired for
592   /// StreamSym.
593   static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
594                                                 SymbolRef StreamSym,
595                                                 CheckerContext &C);
596 };
597 
598 struct StreamOperationEvaluator {
599   SValBuilder &SVB;
600   const ASTContext &ACtx;
601 
602   SymbolRef StreamSym;
603   const StreamState *SS = nullptr;
604   const CallExpr *CE = nullptr;
605   StreamErrorState NewES;
606 
607   StreamOperationEvaluator(CheckerContext &C)
608       : SVB(C.getSValBuilder()), ACtx(C.getASTContext()) {
609     ;
610   }
611 
612   bool Init(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C,
613             ProgramStateRef State) {
614     StreamSym = getStreamArg(Desc, Call).getAsSymbol();
615     if (!StreamSym)
616       return false;
617     SS = State->get<StreamMap>(StreamSym);
618     if (!SS)
619       return false;
620     NewES = SS->ErrorState;
621     CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
622     if (!CE)
623       return false;
624 
625     assertStreamStateOpened(SS);
626 
627     return true;
628   }
629 
630   bool isStreamEof() const { return SS->ErrorState == ErrorFEof; }
631 
632   NonLoc getZeroVal(const CallEvent &Call) {
633     return *SVB.makeZeroVal(Call.getResultType()).getAs<NonLoc>();
634   }
635 
636   ProgramStateRef setStreamState(ProgramStateRef State,
637                                  const StreamState &NewSS) {
638     NewES = NewSS.ErrorState;
639     return State->set<StreamMap>(StreamSym, NewSS);
640   }
641 
642   ProgramStateRef makeAndBindRetVal(ProgramStateRef State, CheckerContext &C) {
643     NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
644     return State->BindExpr(CE, C.getLocationContext(), RetVal);
645   }
646 
647   ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C,
648                                   uint64_t Val) {
649     return State->BindExpr(CE, C.getLocationContext(),
650                            SVB.makeIntVal(Val, CE->getCallReturnType(ACtx)));
651   }
652 
653   ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C,
654                                   SVal Val) {
655     return State->BindExpr(CE, C.getLocationContext(), Val);
656   }
657 
658   ProgramStateRef bindNullReturnValue(ProgramStateRef State,
659                                       CheckerContext &C) {
660     return State->BindExpr(CE, C.getLocationContext(),
661                            C.getSValBuilder().makeNullWithType(CE->getType()));
662   }
663 
664   ProgramStateRef assumeBinOpNN(ProgramStateRef State,
665                                 BinaryOperator::Opcode Op, NonLoc LHS,
666                                 NonLoc RHS) {
667     auto Cond = SVB.evalBinOpNN(State, Op, LHS, RHS, SVB.getConditionType())
668                     .getAs<DefinedOrUnknownSVal>();
669     if (!Cond)
670       return nullptr;
671     return State->assume(*Cond, true);
672   }
673 
674   ConstraintManager::ProgramStatePair
675   makeRetValAndAssumeDual(ProgramStateRef State, CheckerContext &C) {
676     DefinedSVal RetVal = makeRetVal(C, CE);
677     State = State->BindExpr(CE, C.getLocationContext(), RetVal);
678     return C.getConstraintManager().assumeDual(State, RetVal);
679   }
680 
681   const NoteTag *getFailureNoteTag(const StreamChecker *Ch, CheckerContext &C) {
682     bool SetFeof = NewES.FEof && !SS->ErrorState.FEof;
683     bool SetFerror = NewES.FError && !SS->ErrorState.FError;
684     if (SetFeof && !SetFerror)
685       return Ch->constructSetEofNoteTag(C, StreamSym);
686     if (!SetFeof && SetFerror)
687       return Ch->constructSetErrorNoteTag(C, StreamSym);
688     if (SetFeof && SetFerror)
689       return Ch->constructSetEofOrErrorNoteTag(C, StreamSym);
690     return nullptr;
691   }
692 };
693 
694 } // end anonymous namespace
695 
696 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,
697                                                       SymbolRef StreamSym,
698                                                       CheckerContext &C) {
699   ProgramStateRef State = N->getState();
700   // When bug type is resource leak, exploded node N may not have state info
701   // for leaked file descriptor, but predecessor should have it.
702   if (!State->get<StreamMap>(StreamSym))
703     N = N->getFirstPred();
704 
705   const ExplodedNode *Pred = N;
706   while (N) {
707     State = N->getState();
708     if (!State->get<StreamMap>(StreamSym))
709       return Pred;
710     Pred = N;
711     N = N->getFirstPred();
712   }
713 
714   return nullptr;
715 }
716 
717 static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
718                                   const CallEvent &Call,
719                                   ArrayRef<unsigned int> EscapingArgs) {
720   const auto *CE = Call.getOriginExpr();
721 
722   SmallVector<SVal> EscapingVals;
723   EscapingVals.reserve(EscapingArgs.size());
724   for (auto EscArgIdx : EscapingArgs)
725     EscapingVals.push_back(Call.getArgSVal(EscArgIdx));
726   State = State->invalidateRegions(EscapingVals, CE, C.blockCount(),
727                                    C.getLocationContext(),
728                                    /*CausesPointerEscape=*/false);
729   return State;
730 }
731 
732 //===----------------------------------------------------------------------===//
733 // Methods of StreamChecker.
734 //===----------------------------------------------------------------------===//
735 
736 void StreamChecker::checkPreCall(const CallEvent &Call,
737                                  CheckerContext &C) const {
738   initMacroValues(C);
739   initVaListType(C);
740 
741   const FnDescription *Desc = lookupFn(Call);
742   if (!Desc || !Desc->PreFn)
743     return;
744 
745   Desc->PreFn(this, Desc, Call, C);
746 }
747 
748 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
749   const FnDescription *Desc = lookupFn(Call);
750   if (!Desc && TestMode)
751     Desc = FnTestDescriptions.lookup(Call);
752   if (!Desc || !Desc->EvalFn)
753     return false;
754 
755   Desc->EvalFn(this, Desc, Call, C);
756 
757   return C.isDifferent();
758 }
759 
760 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
761                               CheckerContext &C) const {
762   ProgramStateRef State = C.getState();
763   const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
764   if (!CE)
765     return;
766 
767   DefinedSVal RetVal = makeRetVal(C, CE);
768   SymbolRef RetSym = RetVal.getAsSymbol();
769   assert(RetSym && "RetVal must be a symbol here.");
770 
771   State = State->BindExpr(CE, C.getLocationContext(), RetVal);
772 
773   // Bifurcate the state into two: one with a valid FILE* pointer, the other
774   // with a NULL.
775   ProgramStateRef StateNotNull, StateNull;
776   std::tie(StateNotNull, StateNull) =
777       C.getConstraintManager().assumeDual(State, RetVal);
778 
779   StateNotNull =
780       StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
781   StateNull =
782       StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
783 
784   C.addTransition(StateNotNull,
785                   constructLeakNoteTag(C, RetSym, "Stream opened here"));
786   C.addTransition(StateNull);
787 }
788 
789 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
790                                CheckerContext &C) const {
791   // Do not allow NULL as passed stream pointer but allow a closed stream.
792   ProgramStateRef State = C.getState();
793   State = ensureStreamNonNull(getStreamArg(Desc, Call),
794                               Call.getArgExpr(Desc->StreamArgNo), C, State);
795   if (!State)
796     return;
797 
798   C.addTransition(State);
799 }
800 
801 void StreamChecker::evalFreopen(const FnDescription *Desc,
802                                 const CallEvent &Call,
803                                 CheckerContext &C) const {
804   ProgramStateRef State = C.getState();
805 
806   auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
807   if (!CE)
808     return;
809 
810   std::optional<DefinedSVal> StreamVal =
811       getStreamArg(Desc, Call).getAs<DefinedSVal>();
812   if (!StreamVal)
813     return;
814 
815   SymbolRef StreamSym = StreamVal->getAsSymbol();
816   // Do not care about concrete values for stream ("(FILE *)0x12345"?).
817   // FIXME: Can be stdin, stdout, stderr such values?
818   if (!StreamSym)
819     return;
820 
821   // Do not handle untracked stream. It is probably escaped.
822   if (!State->get<StreamMap>(StreamSym))
823     return;
824 
825   // Generate state for non-failed case.
826   // Return value is the passed stream pointer.
827   // According to the documentations, the stream is closed first
828   // but any close error is ignored. The state changes to (or remains) opened.
829   ProgramStateRef StateRetNotNull =
830       State->BindExpr(CE, C.getLocationContext(), *StreamVal);
831   // Generate state for NULL return value.
832   // Stream switches to OpenFailed state.
833   ProgramStateRef StateRetNull =
834       State->BindExpr(CE, C.getLocationContext(),
835                       C.getSValBuilder().makeNullWithType(CE->getType()));
836 
837   StateRetNotNull =
838       StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
839   StateRetNull =
840       StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
841 
842   C.addTransition(StateRetNotNull,
843                   constructLeakNoteTag(C, StreamSym, "Stream reopened here"));
844   C.addTransition(StateRetNull);
845 }
846 
847 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
848                                CheckerContext &C) const {
849   ProgramStateRef State = C.getState();
850   StreamOperationEvaluator E(C);
851   if (!E.Init(Desc, Call, C, State))
852     return;
853 
854   // Close the File Descriptor.
855   // Regardless if the close fails or not, stream becomes "closed"
856   // and can not be used any more.
857   State = E.setStreamState(State, StreamState::getClosed(Desc));
858 
859   // Return 0 on success, EOF on failure.
860   C.addTransition(E.bindReturnValue(State, C, 0));
861   C.addTransition(E.bindReturnValue(State, C, *EofVal));
862 }
863 
864 void StreamChecker::preRead(const FnDescription *Desc, const CallEvent &Call,
865                             CheckerContext &C) const {
866   ProgramStateRef State = C.getState();
867   SVal StreamVal = getStreamArg(Desc, Call);
868   State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
869                               State);
870   if (!State)
871     return;
872   State = ensureStreamOpened(StreamVal, C, State);
873   if (!State)
874     return;
875   State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
876   if (!State)
877     return;
878 
879   SymbolRef Sym = StreamVal.getAsSymbol();
880   if (Sym && State->get<StreamMap>(Sym)) {
881     const StreamState *SS = State->get<StreamMap>(Sym);
882     if (SS->ErrorState & ErrorFEof)
883       reportFEofWarning(Sym, C, State);
884   } else {
885     C.addTransition(State);
886   }
887 }
888 
889 void StreamChecker::preWrite(const FnDescription *Desc, const CallEvent &Call,
890                              CheckerContext &C) const {
891   ProgramStateRef State = C.getState();
892   SVal StreamVal = getStreamArg(Desc, Call);
893   State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
894                               State);
895   if (!State)
896     return;
897   State = ensureStreamOpened(StreamVal, C, State);
898   if (!State)
899     return;
900   State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
901   if (!State)
902     return;
903 
904   C.addTransition(State);
905 }
906 
907 void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
908                                     const CallEvent &Call, CheckerContext &C,
909                                     bool IsFread) const {
910   ProgramStateRef State = C.getState();
911   StreamOperationEvaluator E(C);
912   if (!E.Init(Desc, Call, C, State))
913     return;
914 
915   std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();
916   if (!SizeVal)
917     return;
918   std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>();
919   if (!NMembVal)
920     return;
921 
922   // C'99 standard, §7.19.8.1.3, the return value of fread:
923   // The fread function returns the number of elements successfully read, which
924   // may be less than nmemb if a read error or end-of-file is encountered. If
925   // size or nmemb is zero, fread returns zero and the contents of the array and
926   // the state of the stream remain unchanged.
927   if (State->isNull(*SizeVal).isConstrainedTrue() ||
928       State->isNull(*NMembVal).isConstrainedTrue()) {
929     // This is the "size or nmemb is zero" case.
930     // Just return 0, do nothing more (not clear the error flags).
931     C.addTransition(E.bindReturnValue(State, C, 0));
932     return;
933   }
934 
935   // At read, invalidate the buffer in any case of error or success,
936   // except if EOF was already present.
937   if (IsFread && !E.isStreamEof())
938     State = escapeArgs(State, C, Call, {0});
939 
940   // Generate a transition for the success state.
941   // If we know the state to be FEOF at fread, do not add a success state.
942   if (!IsFread || !E.isStreamEof()) {
943     ProgramStateRef StateNotFailed =
944         State->BindExpr(E.CE, C.getLocationContext(), *NMembVal);
945     StateNotFailed =
946         E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
947     C.addTransition(StateNotFailed);
948   }
949 
950   // Add transition for the failed state.
951   // At write, add failure case only if "pedantic mode" is on.
952   if (!IsFread && !PedanticMode)
953     return;
954 
955   NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
956   ProgramStateRef StateFailed =
957       State->BindExpr(E.CE, C.getLocationContext(), RetVal);
958   StateFailed = E.assumeBinOpNN(StateFailed, BO_LT, RetVal, *NMembVal);
959   if (!StateFailed)
960     return;
961 
962   StreamErrorState NewES;
963   if (IsFread)
964     NewES = E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
965   else
966     NewES = ErrorFError;
967   // If a (non-EOF) error occurs, the resulting value of the file position
968   // indicator for the stream is indeterminate.
969   StateFailed = E.setStreamState(
970       StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
971   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
972 }
973 
974 void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call,
975                               CheckerContext &C, bool SingleChar) const {
976   // `fgetc` returns the read character on success, otherwise returns EOF.
977   // `fgets` returns the read buffer address on success, otherwise returns NULL.
978 
979   ProgramStateRef State = C.getState();
980   StreamOperationEvaluator E(C);
981   if (!E.Init(Desc, Call, C, State))
982     return;
983 
984   if (!E.isStreamEof()) {
985     // If there was already EOF, assume that read buffer is not changed.
986     // Otherwise it may change at success or failure.
987     State = escapeArgs(State, C, Call, {0});
988     if (SingleChar) {
989       // Generate a transition for the success state of `fgetc`.
990       NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
991       ProgramStateRef StateNotFailed =
992           State->BindExpr(E.CE, C.getLocationContext(), RetVal);
993       // The returned 'unsigned char' of `fgetc` is converted to 'int',
994       // so we need to check if it is in range [0, 255].
995       StateNotFailed = StateNotFailed->assumeInclusiveRange(
996           RetVal,
997           E.SVB.getBasicValueFactory().getValue(0, E.ACtx.UnsignedCharTy),
998           E.SVB.getBasicValueFactory().getMaxValue(E.ACtx.UnsignedCharTy),
999           true);
1000       if (!StateNotFailed)
1001         return;
1002       C.addTransition(StateNotFailed);
1003     } else {
1004       // Generate a transition for the success state of `fgets`.
1005       std::optional<DefinedSVal> GetBuf =
1006           Call.getArgSVal(0).getAs<DefinedSVal>();
1007       if (!GetBuf)
1008         return;
1009       ProgramStateRef StateNotFailed =
1010           State->BindExpr(E.CE, C.getLocationContext(), *GetBuf);
1011       StateNotFailed =
1012           E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
1013       C.addTransition(StateNotFailed);
1014     }
1015   }
1016 
1017   // Add transition for the failed state.
1018   ProgramStateRef StateFailed;
1019   if (SingleChar)
1020     StateFailed = E.bindReturnValue(State, C, *EofVal);
1021   else
1022     StateFailed = E.bindNullReturnValue(State, C);
1023 
1024   // If a (non-EOF) error occurs, the resulting value of the file position
1025   // indicator for the stream is indeterminate.
1026   StreamErrorState NewES =
1027       E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
1028   StateFailed = E.setStreamState(
1029       StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
1030   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1031 }
1032 
1033 void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,
1034                               CheckerContext &C, bool IsSingleChar) const {
1035   // `fputc` returns the written character on success, otherwise returns EOF.
1036   // `fputs` returns a nonnegative value on success, otherwise returns EOF.
1037 
1038   ProgramStateRef State = C.getState();
1039   StreamOperationEvaluator E(C);
1040   if (!E.Init(Desc, Call, C, State))
1041     return;
1042 
1043   if (IsSingleChar) {
1044     // Generate a transition for the success state of `fputc`.
1045     std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
1046     if (!PutVal)
1047       return;
1048     ProgramStateRef StateNotFailed =
1049         State->BindExpr(E.CE, C.getLocationContext(), *PutVal);
1050     StateNotFailed =
1051         E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
1052     C.addTransition(StateNotFailed);
1053   } else {
1054     // Generate a transition for the success state of `fputs`.
1055     NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1056     ProgramStateRef StateNotFailed =
1057         State->BindExpr(E.CE, C.getLocationContext(), RetVal);
1058     StateNotFailed =
1059         E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1060     if (!StateNotFailed)
1061       return;
1062     StateNotFailed =
1063         E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
1064     C.addTransition(StateNotFailed);
1065   }
1066 
1067   if (!PedanticMode)
1068     return;
1069 
1070   // Add transition for the failed state. The resulting value of the file
1071   // position indicator for the stream is indeterminate.
1072   ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
1073   StateFailed = E.setStreamState(
1074       StateFailed, StreamState::getOpened(Desc, ErrorFError, true));
1075   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1076 }
1077 
1078 void StreamChecker::evalFprintf(const FnDescription *Desc,
1079                                 const CallEvent &Call,
1080                                 CheckerContext &C) const {
1081   if (Call.getNumArgs() < 2)
1082     return;
1083 
1084   ProgramStateRef State = C.getState();
1085   StreamOperationEvaluator E(C);
1086   if (!E.Init(Desc, Call, C, State))
1087     return;
1088 
1089   NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1090   State = State->BindExpr(E.CE, C.getLocationContext(), RetVal);
1091   auto Cond =
1092       E.SVB
1093           .evalBinOp(State, BO_GE, RetVal, E.SVB.makeZeroVal(E.ACtx.IntTy),
1094                      E.SVB.getConditionType())
1095           .getAs<DefinedOrUnknownSVal>();
1096   if (!Cond)
1097     return;
1098   ProgramStateRef StateNotFailed, StateFailed;
1099   std::tie(StateNotFailed, StateFailed) = State->assume(*Cond);
1100 
1101   StateNotFailed =
1102       E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
1103   C.addTransition(StateNotFailed);
1104 
1105   if (!PedanticMode)
1106     return;
1107 
1108   // Add transition for the failed state. The resulting value of the file
1109   // position indicator for the stream is indeterminate.
1110   StateFailed = E.setStreamState(
1111       StateFailed, StreamState::getOpened(Desc, ErrorFError, true));
1112   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1113 }
1114 
1115 void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
1116                                CheckerContext &C) const {
1117   if (Call.getNumArgs() < 2)
1118     return;
1119 
1120   ProgramStateRef State = C.getState();
1121   StreamOperationEvaluator E(C);
1122   if (!E.Init(Desc, Call, C, State))
1123     return;
1124 
1125   // Add the success state.
1126   // In this context "success" means there is not an EOF or other read error
1127   // before any item is matched in 'fscanf'. But there may be match failure,
1128   // therefore return value can be 0 or greater.
1129   // It is not specified what happens if some items (not all) are matched and
1130   // then EOF or read error happens. Now this case is handled like a "success"
1131   // case, and no error flags are set on the stream. This is probably not
1132   // accurate, and the POSIX documentation does not tell more.
1133   if (!E.isStreamEof()) {
1134     NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1135     ProgramStateRef StateNotFailed =
1136         State->BindExpr(E.CE, C.getLocationContext(), RetVal);
1137     StateNotFailed =
1138         E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1139     if (!StateNotFailed)
1140       return;
1141 
1142     if (auto const *Callee = Call.getCalleeIdentifier();
1143         !Callee || !Callee->getName().equals("vfscanf")) {
1144       SmallVector<unsigned int> EscArgs;
1145       for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))
1146         EscArgs.push_back(EscArg);
1147       StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);
1148     }
1149 
1150     if (StateNotFailed)
1151       C.addTransition(StateNotFailed);
1152   }
1153 
1154   // Add transition for the failed state.
1155   // Error occurs if nothing is matched yet and reading the input fails.
1156   // Error can be EOF, or other error. At "other error" FERROR or 'errno' can
1157   // be set but it is not further specified if all are required to be set.
1158   // Documentation does not mention, but file position will be set to
1159   // indeterminate similarly as at 'fread'.
1160   ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
1161   StreamErrorState NewES =
1162       E.isStreamEof() ? ErrorFEof : ErrorNone | ErrorFEof | ErrorFError;
1163   StateFailed = E.setStreamState(
1164       StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
1165   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1166 }
1167 
1168 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
1169                                CheckerContext &C) const {
1170   ProgramStateRef State = C.getState();
1171   StreamOperationEvaluator E(C);
1172   if (!E.Init(Desc, Call, C, State))
1173     return;
1174 
1175   // Generate a transition for the success state.
1176   std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
1177   if (!PutVal)
1178     return;
1179   ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, *PutVal);
1180   StateNotFailed =
1181       E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
1182   C.addTransition(StateNotFailed);
1183 
1184   // Add transition for the failed state.
1185   // Failure of 'ungetc' does not result in feof or ferror state.
1186   // If the PutVal has value of EofVal the function should "fail", but this is
1187   // the same transition as the success state.
1188   // In this case only one state transition is added by the analyzer (the two
1189   // new states may be similar).
1190   ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
1191   StateFailed = E.setStreamState(StateFailed, StreamState::getOpened(Desc));
1192   C.addTransition(StateFailed);
1193 }
1194 
1195 void StreamChecker::evalGetdelim(const FnDescription *Desc,
1196                                  const CallEvent &Call,
1197                                  CheckerContext &C) const {
1198   ProgramStateRef State = C.getState();
1199   StreamOperationEvaluator E(C);
1200   if (!E.Init(Desc, Call, C, State))
1201     return;
1202 
1203   // Upon successful completion, the getline() and getdelim() functions shall
1204   // return the number of bytes written into the buffer.
1205   // If the end-of-file indicator for the stream is set, the function shall
1206   // return -1.
1207   // If an error occurs, the function shall return -1 and set 'errno'.
1208 
1209   if (!E.isStreamEof()) {
1210     // Escape buffer and size (may change by the call).
1211     // May happen even at error (partial read?).
1212     State = escapeArgs(State, C, Call, {0, 1});
1213 
1214     // Add transition for the successful state.
1215     NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1216     ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, RetVal);
1217     StateNotFailed =
1218         E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1219 
1220     // On success, a buffer is allocated.
1221     auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State);
1222     if (NewLinePtr && isa<DefinedOrUnknownSVal>(*NewLinePtr))
1223       StateNotFailed = StateNotFailed->assume(
1224           NewLinePtr->castAs<DefinedOrUnknownSVal>(), true);
1225 
1226     // The buffer size `*n` must be enough to hold the whole line, and
1227     // greater than the return value, since it has to account for '\0'.
1228     SVal SizePtrSval = Call.getArgSVal(1);
1229     auto NVal = getPointeeVal(SizePtrSval, State);
1230     if (NVal && isa<NonLoc>(*NVal)) {
1231       StateNotFailed = E.assumeBinOpNN(StateNotFailed, BO_GT,
1232                                        NVal->castAs<NonLoc>(), RetVal);
1233       StateNotFailed = E.bindReturnValue(StateNotFailed, C, RetVal);
1234     }
1235     if (!StateNotFailed)
1236       return;
1237     C.addTransition(StateNotFailed);
1238   }
1239 
1240   // Add transition for the failed state.
1241   // If a (non-EOF) error occurs, the resulting value of the file position
1242   // indicator for the stream is indeterminate.
1243   ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
1244   StreamErrorState NewES =
1245       E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
1246   StateFailed = E.setStreamState(
1247       StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
1248   // On failure, the content of the buffer is undefined.
1249   if (auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State))
1250     StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),
1251                                        C.getLocationContext());
1252   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1253 }
1254 
1255 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
1256                              CheckerContext &C) const {
1257   ProgramStateRef State = C.getState();
1258   SVal StreamVal = getStreamArg(Desc, Call);
1259   State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1260                               State);
1261   if (!State)
1262     return;
1263   State = ensureStreamOpened(StreamVal, C, State);
1264   if (!State)
1265     return;
1266   State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
1267   if (!State)
1268     return;
1269 
1270   C.addTransition(State);
1271 }
1272 
1273 void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
1274                               CheckerContext &C) const {
1275   ProgramStateRef State = C.getState();
1276   StreamOperationEvaluator E(C);
1277   if (!E.Init(Desc, Call, C, State))
1278     return;
1279 
1280   // Add success state.
1281   ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, 0);
1282   // No failure: Reset the state to opened with no error.
1283   StateNotFailed =
1284       E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
1285   C.addTransition(StateNotFailed);
1286 
1287   if (!PedanticMode)
1288     return;
1289 
1290   // Add failure state.
1291   // At error it is possible that fseek fails but sets none of the error flags.
1292   // If fseek failed, assume that the file position becomes indeterminate in any
1293   // case.
1294   // It is allowed to set the position beyond the end of the file. EOF error
1295   // should not occur.
1296   ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
1297   StateFailed = E.setStreamState(
1298       StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
1299   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1300 }
1301 
1302 void StreamChecker::evalFgetpos(const FnDescription *Desc,
1303                                 const CallEvent &Call,
1304                                 CheckerContext &C) const {
1305   ProgramStateRef State = C.getState();
1306   StreamOperationEvaluator E(C);
1307   if (!E.Init(Desc, Call, C, State))
1308     return;
1309 
1310   ProgramStateRef StateNotFailed, StateFailed;
1311   std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C);
1312   StateNotFailed = escapeArgs(StateNotFailed, C, Call, {1});
1313 
1314   // This function does not affect the stream state.
1315   // Still we add success and failure state with the appropriate return value.
1316   // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
1317   C.addTransition(StateNotFailed);
1318   C.addTransition(StateFailed);
1319 }
1320 
1321 void StreamChecker::evalFsetpos(const FnDescription *Desc,
1322                                 const CallEvent &Call,
1323                                 CheckerContext &C) const {
1324   ProgramStateRef State = C.getState();
1325   StreamOperationEvaluator E(C);
1326   if (!E.Init(Desc, Call, C, State))
1327     return;
1328 
1329   ProgramStateRef StateNotFailed, StateFailed;
1330   std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C);
1331 
1332   StateNotFailed = E.setStreamState(
1333       StateNotFailed, StreamState::getOpened(Desc, ErrorNone, false));
1334   C.addTransition(StateNotFailed);
1335 
1336   if (!PedanticMode)
1337     return;
1338 
1339   // At failure ferror could be set.
1340   // The standards do not tell what happens with the file position at failure.
1341   // But we can assume that it is dangerous to make a next I/O operation after
1342   // the position was not set correctly (similar to 'fseek').
1343   StateFailed = E.setStreamState(
1344       StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
1345 
1346   C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
1347 }
1348 
1349 void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,
1350                               CheckerContext &C) const {
1351   ProgramStateRef State = C.getState();
1352   StreamOperationEvaluator E(C);
1353   if (!E.Init(Desc, Call, C, State))
1354     return;
1355 
1356   NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1357   ProgramStateRef StateNotFailed =
1358       State->BindExpr(E.CE, C.getLocationContext(), RetVal);
1359   StateNotFailed =
1360       E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1361   if (!StateNotFailed)
1362     return;
1363 
1364   ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
1365 
1366   // This function does not affect the stream state.
1367   // Still we add success and failure state with the appropriate return value.
1368   // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
1369   C.addTransition(StateNotFailed);
1370   C.addTransition(StateFailed);
1371 }
1372 
1373 void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,
1374                                CheckerContext &C) const {
1375   ProgramStateRef State = C.getState();
1376   StreamOperationEvaluator E(C);
1377   if (!E.Init(Desc, Call, C, State))
1378     return;
1379 
1380   State =
1381       E.setStreamState(State, StreamState::getOpened(Desc, ErrorNone, false));
1382   C.addTransition(State);
1383 }
1384 
1385 void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call,
1386                               CheckerContext &C) const {
1387   ProgramStateRef State = C.getState();
1388   SVal StreamVal = getStreamArg(Desc, Call);
1389   std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
1390   if (!Stream)
1391     return;
1392 
1393   ProgramStateRef StateNotNull, StateNull;
1394   std::tie(StateNotNull, StateNull) =
1395       C.getConstraintManager().assumeDual(State, *Stream);
1396   if (StateNotNull && !StateNull)
1397     ensureStreamOpened(StreamVal, C, StateNotNull);
1398 }
1399 
1400 void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call,
1401                                CheckerContext &C) const {
1402   ProgramStateRef State = C.getState();
1403   SVal StreamVal = getStreamArg(Desc, Call);
1404   std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
1405   if (!Stream)
1406     return;
1407 
1408   // Skip if the stream can be both NULL and non-NULL.
1409   ProgramStateRef StateNotNull, StateNull;
1410   std::tie(StateNotNull, StateNull) =
1411       C.getConstraintManager().assumeDual(State, *Stream);
1412   if (StateNotNull && StateNull)
1413     return;
1414   if (StateNotNull && !StateNull)
1415     State = StateNotNull;
1416   else
1417     State = StateNull;
1418 
1419   const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1420   if (!CE)
1421     return;
1422 
1423   // `fflush` returns EOF on failure, otherwise returns 0.
1424   ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
1425   ProgramStateRef StateNotFailed = bindInt(0, State, C, CE);
1426 
1427   // Clear error states if `fflush` returns 0, but retain their EOF flags.
1428   auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym,
1429                                                        const StreamState *SS) {
1430     if (SS->ErrorState & ErrorFError) {
1431       StreamErrorState NewES =
1432           (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone;
1433       StreamState NewSS = StreamState::getOpened(Desc, NewES, false);
1434       StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS);
1435     }
1436   };
1437 
1438   if (StateNotNull && !StateNull) {
1439     // Skip if the input stream's state is unknown, open-failed or closed.
1440     if (SymbolRef StreamSym = StreamVal.getAsSymbol()) {
1441       const StreamState *SS = State->get<StreamMap>(StreamSym);
1442       if (SS) {
1443         assert(SS->isOpened() && "Stream is expected to be opened");
1444         ClearErrorInNotFailed(StreamSym, SS);
1445       } else
1446         return;
1447     }
1448   } else {
1449     // Clear error states for all streams.
1450     const StreamMapTy &Map = StateNotFailed->get<StreamMap>();
1451     for (const auto &I : Map) {
1452       SymbolRef Sym = I.first;
1453       const StreamState &SS = I.second;
1454       if (SS.isOpened())
1455         ClearErrorInNotFailed(Sym, &SS);
1456     }
1457   }
1458 
1459   C.addTransition(StateNotFailed);
1460   C.addTransition(StateFailed);
1461 }
1462 
1463 void StreamChecker::evalClearerr(const FnDescription *Desc,
1464                                  const CallEvent &Call,
1465                                  CheckerContext &C) const {
1466   ProgramStateRef State = C.getState();
1467   StreamOperationEvaluator E(C);
1468   if (!E.Init(Desc, Call, C, State))
1469     return;
1470 
1471   // FilePositionIndeterminate is not cleared.
1472   State = E.setStreamState(
1473       State,
1474       StreamState::getOpened(Desc, ErrorNone, E.SS->FilePositionIndeterminate));
1475   C.addTransition(State);
1476 }
1477 
1478 void StreamChecker::evalFeofFerror(const FnDescription *Desc,
1479                                    const CallEvent &Call, CheckerContext &C,
1480                                    const StreamErrorState &ErrorKind) const {
1481   ProgramStateRef State = C.getState();
1482   StreamOperationEvaluator E(C);
1483   if (!E.Init(Desc, Call, C, State))
1484     return;
1485 
1486   if (E.SS->ErrorState & ErrorKind) {
1487     // Execution path with error of ErrorKind.
1488     // Function returns true.
1489     // From now on it is the only one error state.
1490     ProgramStateRef TrueState = bindAndAssumeTrue(State, C, E.CE);
1491     C.addTransition(E.setStreamState(
1492         TrueState, StreamState::getOpened(Desc, ErrorKind,
1493                                           E.SS->FilePositionIndeterminate &&
1494                                               !ErrorKind.isFEof())));
1495   }
1496   if (StreamErrorState NewES = E.SS->ErrorState & (~ErrorKind)) {
1497     // Execution path(s) with ErrorKind not set.
1498     // Function returns false.
1499     // New error state is everything before minus ErrorKind.
1500     ProgramStateRef FalseState = E.bindReturnValue(State, C, 0);
1501     C.addTransition(E.setStreamState(
1502         FalseState,
1503         StreamState::getOpened(
1504             Desc, NewES, E.SS->FilePositionIndeterminate && !NewES.isFEof())));
1505   }
1506 }
1507 
1508 void StreamChecker::evalFileno(const FnDescription *Desc, const CallEvent &Call,
1509                                CheckerContext &C) const {
1510   // Fileno should fail only if the passed pointer is invalid.
1511   // Some of the preconditions are checked already in preDefault.
1512   // Here we can assume that the operation does not fail, because if we
1513   // introduced a separate branch where fileno() returns -1, then it would cause
1514   // many unexpected and unwanted warnings in situations where fileno() is
1515   // called on valid streams.
1516   // The stream error states are not modified by 'fileno', and 'errno' is also
1517   // left unchanged (so this evalCall does not invalidate it, but we have a
1518   // custom evalCall instead of the default that would invalidate it).
1519   ProgramStateRef State = C.getState();
1520   StreamOperationEvaluator E(C);
1521   if (!E.Init(Desc, Call, C, State))
1522     return;
1523 
1524   NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1525   State = State->BindExpr(E.CE, C.getLocationContext(), RetVal);
1526   State = E.assumeBinOpNN(State, BO_GE, RetVal, E.getZeroVal(Call));
1527   if (!State)
1528     return;
1529 
1530   C.addTransition(State);
1531 }
1532 
1533 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
1534                                CheckerContext &C) const {
1535   ProgramStateRef State = C.getState();
1536   SVal StreamVal = getStreamArg(Desc, Call);
1537   State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1538                               State);
1539   if (!State)
1540     return;
1541   State = ensureStreamOpened(StreamVal, C, State);
1542   if (!State)
1543     return;
1544 
1545   C.addTransition(State);
1546 }
1547 
1548 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
1549                                       const CallEvent &Call, CheckerContext &C,
1550                                       const StreamErrorState &ErrorKind,
1551                                       bool Indeterminate) const {
1552   ProgramStateRef State = C.getState();
1553   SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1554   assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
1555   const StreamState *SS = State->get<StreamMap>(StreamSym);
1556   assert(SS && "Stream should be tracked by the checker.");
1557   State = State->set<StreamMap>(
1558       StreamSym,
1559       StreamState::getOpened(SS->LastOperation, ErrorKind, Indeterminate));
1560   C.addTransition(State);
1561 }
1562 
1563 ProgramStateRef
1564 StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
1565                                    CheckerContext &C,
1566                                    ProgramStateRef State) const {
1567   auto Stream = StreamVal.getAs<DefinedSVal>();
1568   if (!Stream)
1569     return State;
1570 
1571   ConstraintManager &CM = C.getConstraintManager();
1572 
1573   ProgramStateRef StateNotNull, StateNull;
1574   std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream);
1575 
1576   if (!StateNotNull && StateNull) {
1577     if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
1578       auto R = std::make_unique<PathSensitiveBugReport>(
1579           BT_FileNull, "Stream pointer might be NULL.", N);
1580       if (StreamE)
1581         bugreporter::trackExpressionValue(N, StreamE, *R);
1582       C.emitReport(std::move(R));
1583     }
1584     return nullptr;
1585   }
1586 
1587   return StateNotNull;
1588 }
1589 
1590 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
1591                                                   CheckerContext &C,
1592                                                   ProgramStateRef State) const {
1593   SymbolRef Sym = StreamVal.getAsSymbol();
1594   if (!Sym)
1595     return State;
1596 
1597   const StreamState *SS = State->get<StreamMap>(Sym);
1598   if (!SS)
1599     return State;
1600 
1601   if (SS->isClosed()) {
1602     // Using a stream pointer after 'fclose' causes undefined behavior
1603     // according to cppreference.com .
1604     ExplodedNode *N = C.generateErrorNode();
1605     if (N) {
1606       C.emitReport(std::make_unique<PathSensitiveBugReport>(
1607           BT_UseAfterClose,
1608           "Stream might be already closed. Causes undefined behaviour.", N));
1609       return nullptr;
1610     }
1611 
1612     return State;
1613   }
1614 
1615   if (SS->isOpenFailed()) {
1616     // Using a stream that has failed to open is likely to cause problems.
1617     // This should usually not occur because stream pointer is NULL.
1618     // But freopen can cause a state when stream pointer remains non-null but
1619     // failed to open.
1620     ExplodedNode *N = C.generateErrorNode();
1621     if (N) {
1622       C.emitReport(std::make_unique<PathSensitiveBugReport>(
1623           BT_UseAfterOpenFailed,
1624           "Stream might be invalid after "
1625           "(re-)opening it has failed. "
1626           "Can cause undefined behaviour.",
1627           N));
1628       return nullptr;
1629     }
1630   }
1631 
1632   return State;
1633 }
1634 
1635 ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate(
1636     SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {
1637   static const char *BugMessage =
1638       "File position of the stream might be 'indeterminate' "
1639       "after a failed operation. "
1640       "Can cause undefined behavior.";
1641 
1642   SymbolRef Sym = StreamVal.getAsSymbol();
1643   if (!Sym)
1644     return State;
1645 
1646   const StreamState *SS = State->get<StreamMap>(Sym);
1647   if (!SS)
1648     return State;
1649 
1650   assert(SS->isOpened() && "First ensure that stream is opened.");
1651 
1652   if (SS->FilePositionIndeterminate) {
1653     if (SS->ErrorState & ErrorFEof) {
1654       // The error is unknown but may be FEOF.
1655       // Continue analysis with the FEOF error state.
1656       // Report warning because the other possible error states.
1657       ExplodedNode *N = C.generateNonFatalErrorNode(State);
1658       if (!N)
1659         return nullptr;
1660 
1661       auto R = std::make_unique<PathSensitiveBugReport>(
1662           BT_IndeterminatePosition, BugMessage, N);
1663       R->markInteresting(Sym);
1664       C.emitReport(std::move(R));
1665       return State->set<StreamMap>(
1666           Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false));
1667     }
1668 
1669     // Known or unknown error state without FEOF possible.
1670     // Stop analysis, report error.
1671     if (ExplodedNode *N = C.generateErrorNode(State)) {
1672       auto R = std::make_unique<PathSensitiveBugReport>(
1673           BT_IndeterminatePosition, BugMessage, N);
1674       R->markInteresting(Sym);
1675       C.emitReport(std::move(R));
1676     }
1677 
1678     return nullptr;
1679   }
1680 
1681   return State;
1682 }
1683 
1684 ProgramStateRef
1685 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
1686                                         ProgramStateRef State) const {
1687   std::optional<nonloc::ConcreteInt> CI =
1688       WhenceVal.getAs<nonloc::ConcreteInt>();
1689   if (!CI)
1690     return State;
1691 
1692   int64_t X = CI->getValue().getSExtValue();
1693   if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal)
1694     return State;
1695 
1696   if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1697     C.emitReport(std::make_unique<PathSensitiveBugReport>(
1698         BT_IllegalWhence,
1699         "The whence argument to fseek() should be "
1700         "SEEK_SET, SEEK_END, or SEEK_CUR.",
1701         N));
1702     return nullptr;
1703   }
1704 
1705   return State;
1706 }
1707 
1708 void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
1709                                       ProgramStateRef State) const {
1710   if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1711     auto R = std::make_unique<PathSensitiveBugReport>(
1712         BT_StreamEof,
1713         "Read function called when stream is in EOF state. "
1714         "Function has no effect.",
1715         N);
1716     R->markInteresting(StreamSym);
1717     C.emitReport(std::move(R));
1718     return;
1719   }
1720   C.addTransition(State);
1721 }
1722 
1723 ExplodedNode *
1724 StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
1725                            CheckerContext &C, ExplodedNode *Pred) const {
1726   ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred);
1727   if (!Err)
1728     return Pred;
1729 
1730   for (SymbolRef LeakSym : LeakedSyms) {
1731     // Resource leaks can result in multiple warning that describe the same kind
1732     // of programming error:
1733     //  void f() {
1734     //    FILE *F = fopen("a.txt");
1735     //    if (rand()) // state split
1736     //      return; // warning
1737     //  } // warning
1738     // While this isn't necessarily true (leaking the same stream could result
1739     // from a different kinds of errors), the reduction in redundant reports
1740     // makes this a worthwhile heuristic.
1741     // FIXME: Add a checker option to turn this uniqueing feature off.
1742     const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C);
1743     assert(StreamOpenNode && "Could not find place of stream opening.");
1744 
1745     PathDiagnosticLocation LocUsedForUniqueing;
1746     if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics())
1747       LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
1748           StreamStmt, C.getSourceManager(),
1749           StreamOpenNode->getLocationContext());
1750 
1751     std::unique_ptr<PathSensitiveBugReport> R =
1752         std::make_unique<PathSensitiveBugReport>(
1753             BT_ResourceLeak,
1754             "Opened stream never closed. Potential resource leak.", Err,
1755             LocUsedForUniqueing,
1756             StreamOpenNode->getLocationContext()->getDecl());
1757     R->markInteresting(LeakSym);
1758     C.emitReport(std::move(R));
1759   }
1760 
1761   return Err;
1762 }
1763 
1764 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1765                                      CheckerContext &C) const {
1766   ProgramStateRef State = C.getState();
1767 
1768   llvm::SmallVector<SymbolRef, 2> LeakedSyms;
1769 
1770   const StreamMapTy &Map = State->get<StreamMap>();
1771   for (const auto &I : Map) {
1772     SymbolRef Sym = I.first;
1773     const StreamState &SS = I.second;
1774     if (!SymReaper.isDead(Sym))
1775       continue;
1776     if (SS.isOpened())
1777       LeakedSyms.push_back(Sym);
1778     State = State->remove<StreamMap>(Sym);
1779   }
1780 
1781   ExplodedNode *N = C.getPredecessor();
1782   if (!LeakedSyms.empty())
1783     N = reportLeaks(LeakedSyms, C, N);
1784 
1785   C.addTransition(State, N);
1786 }
1787 
1788 ProgramStateRef StreamChecker::checkPointerEscape(
1789     ProgramStateRef State, const InvalidatedSymbols &Escaped,
1790     const CallEvent *Call, PointerEscapeKind Kind) const {
1791   // Check for file-handling system call that is not handled by the checker.
1792   // FIXME: The checker should be updated to handle all system calls that take
1793   // 'FILE*' argument. These are now ignored.
1794   if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader())
1795     return State;
1796 
1797   for (SymbolRef Sym : Escaped) {
1798     // The symbol escaped.
1799     // From now the stream can be manipulated in unknown way to the checker,
1800     // it is not possible to handle it any more.
1801     // Optimistically, assume that the corresponding file handle will be closed
1802     // somewhere else.
1803     // Remove symbol from state so the following stream calls on this symbol are
1804     // not handled by the checker.
1805     State = State->remove<StreamMap>(Sym);
1806   }
1807   return State;
1808 }
1809 
1810 //===----------------------------------------------------------------------===//
1811 // Checker registration.
1812 //===----------------------------------------------------------------------===//
1813 
1814 void ento::registerStreamChecker(CheckerManager &Mgr) {
1815   auto *Checker = Mgr.registerChecker<StreamChecker>();
1816   Checker->PedanticMode =
1817       Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "Pedantic");
1818 }
1819 
1820 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {
1821   return true;
1822 }
1823 
1824 void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
1825   auto *Checker = Mgr.getChecker<StreamChecker>();
1826   Checker->TestMode = true;
1827 }
1828 
1829 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
1830   return true;
1831 }
1832