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