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