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