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