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