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
isNoError__anon08cb4eb70111::StreamErrorState53 bool isNoError() const { return NoError && !FEof && !FError; }
isFEof__anon08cb4eb70111::StreamErrorState54 bool isFEof() const { return !NoError && FEof && !FError; }
isFError__anon08cb4eb70111::StreamErrorState55 bool isFError() const { return !NoError && !FEof && FError; }
56
operator ==__anon08cb4eb70111::StreamErrorState57 bool operator==(const StreamErrorState &ES) const {
58 return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError;
59 }
60
operator !=__anon08cb4eb70111::StreamErrorState61 bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); }
62
operator |__anon08cb4eb70111::StreamErrorState63 StreamErrorState operator|(const StreamErrorState &E) const {
64 return {NoError || E.NoError, FEof || E.FEof, FError || E.FError};
65 }
66
operator &__anon08cb4eb70111::StreamErrorState67 StreamErrorState operator&(const StreamErrorState &E) const {
68 return {NoError && E.NoError, FEof && E.FEof, FError && E.FError};
69 }
70
operator ~__anon08cb4eb70111::StreamErrorState71 StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; }
72
73 /// Returns if the StreamErrorState is a valid object.
operator bool__anon08cb4eb70111::StreamErrorState74 operator bool() const { return NoError || FEof || FError; }
75
Profile__anon08cb4eb70111::StreamErrorState76 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
StreamState__anon08cb4eb70111::StreamState114 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
isOpened__anon08cb4eb70111::StreamState124 bool isOpened() const { return State == Opened; }
isClosed__anon08cb4eb70111::StreamState125 bool isClosed() const { return State == Closed; }
isOpenFailed__anon08cb4eb70111::StreamState126 bool isOpenFailed() const { return State == OpenFailed; }
127
operator ==__anon08cb4eb70111::StreamState128 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
getOpened__anon08cb4eb70111::StreamState136 static StreamState getOpened(const FnDescription *L,
137 const StreamErrorState &ES = ErrorNone,
138 bool IsFilePositionIndeterminate = false) {
139 return StreamState{L, Opened, ES, IsFilePositionIndeterminate};
140 }
getClosed__anon08cb4eb70111::StreamState141 static StreamState getClosed(const FnDescription *L) {
142 return StreamState{L, Closed, {}, false};
143 }
getOpenFailed__anon08cb4eb70111::StreamState144 static StreamState getOpenFailed(const FnDescription *L) {
145 return StreamState{L, OpenFailed, {}, false};
146 }
147
Profile__anon08cb4eb70111::StreamState148 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.
getStreamArg(const FnDescription * Desc,const CallEvent & Call)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.
makeRetVal(CheckerContext & C,const CallExpr * CE)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
bindAndAssumeTrue(ProgramStateRef State,CheckerContext & C,const CallExpr * CE)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
bindInt(uint64_t Value,ProgramStateRef State,CheckerContext & C,const CallExpr * CE)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
getBT_StreamEof() const237 const BugType *getBT_StreamEof() const { return &BT_StreamEof; }
238
239 private:
240 CallDescriptionMap<FnDescription> FnDescriptions = {
241 {{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
242 {{{"fdopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
243 {{{"freopen"}, 3},
244 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
245 {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
246 {{{"fclose"}, 1},
247 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
248 {{{"fread"}, 4},
249 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
250 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},
251 {{{"fwrite"}, 4},
252 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
253 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},
254 {{{"fgetc"}, 1},
255 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
256 std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
257 {{{"fgets"}, 3},
258 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
259 std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},
260 {{{"fputc"}, 2},
261 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
262 std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
263 {{{"fputs"}, 2},
264 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
265 std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
266 {{{"fprintf"}},
267 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
268 std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
269 {{{"fscanf"}},
270 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
271 std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
272 {{{"ungetc"}, 2},
273 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
274 std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
275 {{{"getdelim"}, 4},
276 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
277 std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
278 {{{"getline"}, 3},
279 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
280 std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
281 {{{"fseek"}, 3},
282 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
283 {{{"fseeko"}, 3},
284 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
285 {{{"ftell"}, 1},
286 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
287 {{{"ftello"}, 1},
288 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
289 {{{"fflush"}, 1},
290 {&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}},
291 {{{"rewind"}, 1},
292 {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
293 {{{"fgetpos"}, 2},
294 {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}},
295 {{{"fsetpos"}, 2},
296 {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},
297 {{{"clearerr"}, 1},
298 {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
299 {{{"feof"}, 1},
300 {&StreamChecker::preDefault,
301 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof),
302 0}},
303 {{{"ferror"}, 1},
304 {&StreamChecker::preDefault,
305 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError),
306 0}},
307 {{{"fileno"}, 1}, {&StreamChecker::preDefault, nullptr, 0}},
308 };
309
310 CallDescriptionMap<FnDescription> FnTestDescriptions = {
311 {{{"StreamTesterChecker_make_feof_stream"}, 1},
312 {nullptr,
313 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof),
314 0}},
315 {{{"StreamTesterChecker_make_ferror_stream"}, 1},
316 {nullptr,
317 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
318 ErrorFError),
319 0}},
320 };
321
322 /// Expanded value of EOF, empty before initialization.
323 mutable std::optional<int> EofVal;
324 /// Expanded value of SEEK_SET, 0 if not found.
325 mutable int SeekSetVal = 0;
326 /// Expanded value of SEEK_CUR, 1 if not found.
327 mutable int SeekCurVal = 1;
328 /// Expanded value of SEEK_END, 2 if not found.
329 mutable int SeekEndVal = 2;
330
331 void evalFopen(const FnDescription *Desc, const CallEvent &Call,
332 CheckerContext &C) const;
333
334 void preFreopen(const FnDescription *Desc, const CallEvent &Call,
335 CheckerContext &C) const;
336 void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
337 CheckerContext &C) const;
338
339 void evalFclose(const FnDescription *Desc, const CallEvent &Call,
340 CheckerContext &C) const;
341
342 void preReadWrite(const FnDescription *Desc, const CallEvent &Call,
343 CheckerContext &C, bool IsRead) const;
344
345 void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
346 CheckerContext &C, bool IsFread) const;
347
348 void evalFgetx(const FnDescription *Desc, const CallEvent &Call,
349 CheckerContext &C, bool SingleChar) const;
350
351 void evalFputx(const FnDescription *Desc, const CallEvent &Call,
352 CheckerContext &C, bool IsSingleChar) const;
353
354 void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
355 CheckerContext &C) const;
356
357 void evalFscanf(const FnDescription *Desc, const CallEvent &Call,
358 CheckerContext &C) const;
359
360 void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
361 CheckerContext &C) const;
362
363 void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
364 CheckerContext &C) const;
365
366 void preFseek(const FnDescription *Desc, const CallEvent &Call,
367 CheckerContext &C) const;
368 void evalFseek(const FnDescription *Desc, const CallEvent &Call,
369 CheckerContext &C) const;
370
371 void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,
372 CheckerContext &C) const;
373
374 void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,
375 CheckerContext &C) const;
376
377 void evalFtell(const FnDescription *Desc, const CallEvent &Call,
378 CheckerContext &C) const;
379
380 void evalRewind(const FnDescription *Desc, const CallEvent &Call,
381 CheckerContext &C) const;
382
383 void preDefault(const FnDescription *Desc, const CallEvent &Call,
384 CheckerContext &C) const;
385
386 void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
387 CheckerContext &C) const;
388
389 void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
390 CheckerContext &C,
391 const StreamErrorState &ErrorKind) const;
392
393 void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
394 CheckerContext &C,
395 const StreamErrorState &ErrorKind) const;
396
397 void preFflush(const FnDescription *Desc, const CallEvent &Call,
398 CheckerContext &C) const;
399
400 void evalFflush(const FnDescription *Desc, const CallEvent &Call,
401 CheckerContext &C) const;
402
403 /// Check that the stream (in StreamVal) is not NULL.
404 /// If it can only be NULL a fatal error is emitted and nullptr returned.
405 /// Otherwise the return value is a new state where the stream is constrained
406 /// to be non-null.
407 ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
408 CheckerContext &C,
409 ProgramStateRef State) const;
410
411 /// Check that the stream is the opened state.
412 /// If the stream is known to be not opened an error is generated
413 /// and nullptr returned, otherwise the original state is returned.
414 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
415 ProgramStateRef State) const;
416
417 /// Check that the stream has not an invalid ("indeterminate") file position,
418 /// generate warning for it.
419 /// (EOF is not an invalid position.)
420 /// The returned state can be nullptr if a fatal error was generated.
421 /// It can return non-null state if the stream has not an invalid position or
422 /// there is execution path with non-invalid position.
423 ProgramStateRef
424 ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C,
425 ProgramStateRef State) const;
426
427 /// Check the legality of the 'whence' argument of 'fseek'.
428 /// Generate error and return nullptr if it is found to be illegal.
429 /// Otherwise returns the state.
430 /// (State is not changed here because the "whence" value is already known.)
431 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
432 ProgramStateRef State) const;
433
434 /// Generate warning about stream in EOF state.
435 /// There will be always a state transition into the passed State,
436 /// by the new non-fatal error node or (if failed) a normal transition,
437 /// to ensure uniform handling.
438 void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
439 ProgramStateRef State) const;
440
441 /// Emit resource leak warnings for the given symbols.
442 /// Createn a non-fatal error node for these, and returns it (if any warnings
443 /// were generated). Return value is non-null.
444 ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
445 CheckerContext &C, ExplodedNode *Pred) const;
446
447 /// Find the description data of the function called by a call event.
448 /// Returns nullptr if no function is recognized.
lookupFn(const CallEvent & Call) const449 const FnDescription *lookupFn(const CallEvent &Call) const {
450 // Recognize "global C functions" with only integral or pointer arguments
451 // (and matching name) as stream functions.
452 if (!Call.isGlobalCFunction())
453 return nullptr;
454 for (auto *P : Call.parameters()) {
455 QualType T = P->getType();
456 if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
457 return nullptr;
458 }
459
460 return FnDescriptions.lookup(Call);
461 }
462
463 /// Generate a message for BugReporterVisitor if the stored symbol is
464 /// marked as interesting by the actual bug report.
constructNoteTag(CheckerContext & C,SymbolRef StreamSym,const std::string & Message) const465 const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym,
466 const std::string &Message) const {
467 return C.getNoteTag([this, StreamSym,
468 Message](PathSensitiveBugReport &BR) -> std::string {
469 if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak)
470 return Message;
471 return "";
472 });
473 }
474
constructSetEofNoteTag(CheckerContext & C,SymbolRef StreamSym) const475 const NoteTag *constructSetEofNoteTag(CheckerContext &C,
476 SymbolRef StreamSym) const {
477 return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
478 if (!BR.isInteresting(StreamSym) ||
479 &BR.getBugType() != this->getBT_StreamEof())
480 return "";
481
482 BR.markNotInteresting(StreamSym);
483
484 return "Assuming stream reaches end-of-file here";
485 });
486 }
487
initMacroValues(CheckerContext & C) const488 void initMacroValues(CheckerContext &C) const {
489 if (EofVal)
490 return;
491
492 if (const std::optional<int> OptInt =
493 tryExpandAsInteger("EOF", C.getPreprocessor()))
494 EofVal = *OptInt;
495 else
496 EofVal = -1;
497 if (const std::optional<int> OptInt =
498 tryExpandAsInteger("SEEK_SET", C.getPreprocessor()))
499 SeekSetVal = *OptInt;
500 if (const std::optional<int> OptInt =
501 tryExpandAsInteger("SEEK_END", C.getPreprocessor()))
502 SeekEndVal = *OptInt;
503 if (const std::optional<int> OptInt =
504 tryExpandAsInteger("SEEK_CUR", C.getPreprocessor()))
505 SeekCurVal = *OptInt;
506 }
507
508 /// Searches for the ExplodedNode where the file descriptor was acquired for
509 /// StreamSym.
510 static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
511 SymbolRef StreamSym,
512 CheckerContext &C);
513 };
514
515 } // end anonymous namespace
516
517 // This map holds the state of a stream.
518 // The stream is identified with a SymbolRef that is created when a stream
519 // opening function is modeled by the checker.
REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap,SymbolRef,StreamState)520 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
521
522 inline void assertStreamStateOpened(const StreamState *SS) {
523 assert(SS->isOpened() && "Stream is expected to be opened");
524 }
525
getAcquisitionSite(const ExplodedNode * N,SymbolRef StreamSym,CheckerContext & C)526 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,
527 SymbolRef StreamSym,
528 CheckerContext &C) {
529 ProgramStateRef State = N->getState();
530 // When bug type is resource leak, exploded node N may not have state info
531 // for leaked file descriptor, but predecessor should have it.
532 if (!State->get<StreamMap>(StreamSym))
533 N = N->getFirstPred();
534
535 const ExplodedNode *Pred = N;
536 while (N) {
537 State = N->getState();
538 if (!State->get<StreamMap>(StreamSym))
539 return Pred;
540 Pred = N;
541 N = N->getFirstPred();
542 }
543
544 return nullptr;
545 }
546
547 //===----------------------------------------------------------------------===//
548 // Methods of StreamChecker.
549 //===----------------------------------------------------------------------===//
550
checkPreCall(const CallEvent & Call,CheckerContext & C) const551 void StreamChecker::checkPreCall(const CallEvent &Call,
552 CheckerContext &C) const {
553 initMacroValues(C);
554
555 const FnDescription *Desc = lookupFn(Call);
556 if (!Desc || !Desc->PreFn)
557 return;
558
559 Desc->PreFn(this, Desc, Call, C);
560 }
561
evalCall(const CallEvent & Call,CheckerContext & C) const562 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
563 const FnDescription *Desc = lookupFn(Call);
564 if (!Desc && TestMode)
565 Desc = FnTestDescriptions.lookup(Call);
566 if (!Desc || !Desc->EvalFn)
567 return false;
568
569 Desc->EvalFn(this, Desc, Call, C);
570
571 return C.isDifferent();
572 }
573
evalFopen(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const574 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
575 CheckerContext &C) const {
576 ProgramStateRef State = C.getState();
577 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
578 if (!CE)
579 return;
580
581 DefinedSVal RetVal = makeRetVal(C, CE);
582 SymbolRef RetSym = RetVal.getAsSymbol();
583 assert(RetSym && "RetVal must be a symbol here.");
584
585 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
586
587 // Bifurcate the state into two: one with a valid FILE* pointer, the other
588 // with a NULL.
589 ProgramStateRef StateNotNull, StateNull;
590 std::tie(StateNotNull, StateNull) =
591 C.getConstraintManager().assumeDual(State, RetVal);
592
593 StateNotNull =
594 StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
595 StateNull =
596 StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
597
598 C.addTransition(StateNotNull,
599 constructNoteTag(C, RetSym, "Stream opened here"));
600 C.addTransition(StateNull);
601 }
602
preFreopen(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const603 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
604 CheckerContext &C) const {
605 // Do not allow NULL as passed stream pointer but allow a closed stream.
606 ProgramStateRef State = C.getState();
607 State = ensureStreamNonNull(getStreamArg(Desc, Call),
608 Call.getArgExpr(Desc->StreamArgNo), C, State);
609 if (!State)
610 return;
611
612 C.addTransition(State);
613 }
614
evalFreopen(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const615 void StreamChecker::evalFreopen(const FnDescription *Desc,
616 const CallEvent &Call,
617 CheckerContext &C) const {
618 ProgramStateRef State = C.getState();
619
620 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
621 if (!CE)
622 return;
623
624 std::optional<DefinedSVal> StreamVal =
625 getStreamArg(Desc, Call).getAs<DefinedSVal>();
626 if (!StreamVal)
627 return;
628
629 SymbolRef StreamSym = StreamVal->getAsSymbol();
630 // Do not care about concrete values for stream ("(FILE *)0x12345"?).
631 // FIXME: Can be stdin, stdout, stderr such values?
632 if (!StreamSym)
633 return;
634
635 // Do not handle untracked stream. It is probably escaped.
636 if (!State->get<StreamMap>(StreamSym))
637 return;
638
639 // Generate state for non-failed case.
640 // Return value is the passed stream pointer.
641 // According to the documentations, the stream is closed first
642 // but any close error is ignored. The state changes to (or remains) opened.
643 ProgramStateRef StateRetNotNull =
644 State->BindExpr(CE, C.getLocationContext(), *StreamVal);
645 // Generate state for NULL return value.
646 // Stream switches to OpenFailed state.
647 ProgramStateRef StateRetNull =
648 State->BindExpr(CE, C.getLocationContext(),
649 C.getSValBuilder().makeNullWithType(CE->getType()));
650
651 StateRetNotNull =
652 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
653 StateRetNull =
654 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
655
656 C.addTransition(StateRetNotNull,
657 constructNoteTag(C, StreamSym, "Stream reopened here"));
658 C.addTransition(StateRetNull);
659 }
660
evalFclose(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const661 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
662 CheckerContext &C) const {
663 ProgramStateRef State = C.getState();
664 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
665 if (!Sym)
666 return;
667
668 const StreamState *SS = State->get<StreamMap>(Sym);
669 if (!SS)
670 return;
671
672 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
673 if (!CE)
674 return;
675
676 assertStreamStateOpened(SS);
677
678 // Close the File Descriptor.
679 // Regardless if the close fails or not, stream becomes "closed"
680 // and can not be used any more.
681 State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc));
682
683 // Return 0 on success, EOF on failure.
684 SValBuilder &SVB = C.getSValBuilder();
685 ProgramStateRef StateSuccess = State->BindExpr(
686 CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy));
687 ProgramStateRef StateFailure =
688 State->BindExpr(CE, C.getLocationContext(),
689 SVB.makeIntVal(*EofVal, C.getASTContext().IntTy));
690
691 C.addTransition(StateSuccess);
692 C.addTransition(StateFailure);
693 }
694
preReadWrite(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C,bool IsRead) const695 void StreamChecker::preReadWrite(const FnDescription *Desc,
696 const CallEvent &Call, CheckerContext &C,
697 bool IsRead) const {
698 ProgramStateRef State = C.getState();
699 SVal StreamVal = getStreamArg(Desc, Call);
700 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
701 State);
702 if (!State)
703 return;
704 State = ensureStreamOpened(StreamVal, C, State);
705 if (!State)
706 return;
707 State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
708 if (!State)
709 return;
710
711 if (!IsRead) {
712 C.addTransition(State);
713 return;
714 }
715
716 SymbolRef Sym = StreamVal.getAsSymbol();
717 if (Sym && State->get<StreamMap>(Sym)) {
718 const StreamState *SS = State->get<StreamMap>(Sym);
719 if (SS->ErrorState & ErrorFEof)
720 reportFEofWarning(Sym, C, State);
721 } else {
722 C.addTransition(State);
723 }
724 }
725
evalFreadFwrite(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C,bool IsFread) const726 void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
727 const CallEvent &Call, CheckerContext &C,
728 bool IsFread) const {
729 ProgramStateRef State = C.getState();
730 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
731 if (!StreamSym)
732 return;
733
734 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
735 if (!CE)
736 return;
737
738 std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();
739 if (!SizeVal)
740 return;
741 std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>();
742 if (!NMembVal)
743 return;
744
745 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
746 if (!OldSS)
747 return;
748
749 assertStreamStateOpened(OldSS);
750
751 // C'99 standard, §7.19.8.1.3, the return value of fread:
752 // The fread function returns the number of elements successfully read, which
753 // may be less than nmemb if a read error or end-of-file is encountered. If
754 // size or nmemb is zero, fread returns zero and the contents of the array and
755 // the state of the stream remain unchanged.
756
757 if (State->isNull(*SizeVal).isConstrainedTrue() ||
758 State->isNull(*NMembVal).isConstrainedTrue()) {
759 // This is the "size or nmemb is zero" case.
760 // Just return 0, do nothing more (not clear the error flags).
761 State = bindInt(0, State, C, CE);
762 C.addTransition(State);
763 return;
764 }
765
766 // Generate a transition for the success state.
767 // If we know the state to be FEOF at fread, do not add a success state.
768 if (!IsFread || (OldSS->ErrorState != ErrorFEof)) {
769 ProgramStateRef StateNotFailed =
770 State->BindExpr(CE, C.getLocationContext(), *NMembVal);
771 StateNotFailed =
772 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
773 C.addTransition(StateNotFailed);
774 }
775
776 // Add transition for the failed state.
777 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
778 ProgramStateRef StateFailed =
779 State->BindExpr(CE, C.getLocationContext(), RetVal);
780 SValBuilder &SVB = C.getSValBuilder();
781 auto Cond =
782 SVB.evalBinOpNN(State, BO_LT, RetVal, *NMembVal, SVB.getConditionType())
783 .getAs<DefinedOrUnknownSVal>();
784 if (!Cond)
785 return;
786 StateFailed = StateFailed->assume(*Cond, true);
787 if (!StateFailed)
788 return;
789
790 StreamErrorState NewES;
791 if (IsFread)
792 NewES =
793 (OldSS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError;
794 else
795 NewES = ErrorFError;
796 // If a (non-EOF) error occurs, the resulting value of the file position
797 // indicator for the stream is indeterminate.
798 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
799 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
800 if (IsFread && OldSS->ErrorState != ErrorFEof)
801 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
802 else
803 C.addTransition(StateFailed);
804 }
805
evalFgetx(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C,bool SingleChar) const806 void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call,
807 CheckerContext &C, bool SingleChar) const {
808 ProgramStateRef State = C.getState();
809 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
810 if (!StreamSym)
811 return;
812
813 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
814 if (!CE)
815 return;
816
817 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
818 if (!OldSS)
819 return;
820
821 assertStreamStateOpened(OldSS);
822
823 // `fgetc` returns the read character on success, otherwise returns EOF.
824 // `fgets` returns the read buffer address on success, otherwise returns NULL.
825
826 if (OldSS->ErrorState != ErrorFEof) {
827 if (SingleChar) {
828 // Generate a transition for the success state of `fgetc`.
829 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
830 ProgramStateRef StateNotFailed =
831 State->BindExpr(CE, C.getLocationContext(), RetVal);
832 SValBuilder &SVB = C.getSValBuilder();
833 ASTContext &ASTC = C.getASTContext();
834 // The returned 'unsigned char' of `fgetc` is converted to 'int',
835 // so we need to check if it is in range [0, 255].
836 auto CondLow =
837 SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ASTC.IntTy),
838 SVB.getConditionType())
839 .getAs<DefinedOrUnknownSVal>();
840 auto CondHigh =
841 SVB.evalBinOp(State, BO_LE, RetVal,
842 SVB.makeIntVal(SVB.getBasicValueFactory()
843 .getMaxValue(ASTC.UnsignedCharTy)
844 .getLimitedValue(),
845 ASTC.IntTy),
846 SVB.getConditionType())
847 .getAs<DefinedOrUnknownSVal>();
848 if (!CondLow || !CondHigh)
849 return;
850 StateNotFailed = StateNotFailed->assume(*CondLow, true);
851 if (!StateNotFailed)
852 return;
853 StateNotFailed = StateNotFailed->assume(*CondHigh, true);
854 if (!StateNotFailed)
855 return;
856 C.addTransition(StateNotFailed);
857 } else {
858 // Generate a transition for the success state of `fgets`.
859 std::optional<DefinedSVal> GetBuf =
860 Call.getArgSVal(0).getAs<DefinedSVal>();
861 if (!GetBuf)
862 return;
863 ProgramStateRef StateNotFailed =
864 State->BindExpr(CE, C.getLocationContext(), *GetBuf);
865 StateNotFailed = StateNotFailed->set<StreamMap>(
866 StreamSym, StreamState::getOpened(Desc));
867 C.addTransition(StateNotFailed);
868 }
869 }
870
871 // Add transition for the failed state.
872 ProgramStateRef StateFailed;
873 if (SingleChar)
874 StateFailed = bindInt(*EofVal, State, C, CE);
875 else
876 StateFailed =
877 State->BindExpr(CE, C.getLocationContext(),
878 C.getSValBuilder().makeNullWithType(CE->getType()));
879
880 // If a (non-EOF) error occurs, the resulting value of the file position
881 // indicator for the stream is indeterminate.
882 StreamErrorState NewES =
883 OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError;
884 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
885 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
886 if (OldSS->ErrorState != ErrorFEof)
887 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
888 else
889 C.addTransition(StateFailed);
890 }
891
evalFputx(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C,bool IsSingleChar) const892 void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,
893 CheckerContext &C, bool IsSingleChar) const {
894 ProgramStateRef State = C.getState();
895 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
896 if (!StreamSym)
897 return;
898
899 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
900 if (!CE)
901 return;
902
903 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
904 if (!OldSS)
905 return;
906
907 assertStreamStateOpened(OldSS);
908
909 // `fputc` returns the written character on success, otherwise returns EOF.
910 // `fputs` returns a non negative value on sucecess, otherwise returns EOF.
911
912 if (IsSingleChar) {
913 // Generate a transition for the success state of `fputc`.
914 std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
915 if (!PutVal)
916 return;
917 ProgramStateRef StateNotFailed =
918 State->BindExpr(CE, C.getLocationContext(), *PutVal);
919 StateNotFailed =
920 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
921 C.addTransition(StateNotFailed);
922 } else {
923 // Generate a transition for the success state of `fputs`.
924 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
925 ProgramStateRef StateNotFailed =
926 State->BindExpr(CE, C.getLocationContext(), RetVal);
927 SValBuilder &SVB = C.getSValBuilder();
928 auto &ASTC = C.getASTContext();
929 auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ASTC.IntTy),
930 SVB.getConditionType())
931 .getAs<DefinedOrUnknownSVal>();
932 if (!Cond)
933 return;
934 StateNotFailed = StateNotFailed->assume(*Cond, true);
935 if (!StateNotFailed)
936 return;
937 StateNotFailed =
938 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
939 C.addTransition(StateNotFailed);
940 }
941
942 // Add transition for the failed state. The resulting value of the file
943 // position indicator for the stream is indeterminate.
944 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
945 StreamState NewSS = StreamState::getOpened(Desc, ErrorFError, true);
946 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
947 C.addTransition(StateFailed);
948 }
949
evalFprintf(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const950 void StreamChecker::evalFprintf(const FnDescription *Desc,
951 const CallEvent &Call,
952 CheckerContext &C) const {
953 ProgramStateRef State = C.getState();
954 if (Call.getNumArgs() < 2)
955 return;
956 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
957 if (!StreamSym)
958 return;
959
960 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
961 if (!CE)
962 return;
963
964 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
965 if (!OldSS)
966 return;
967
968 assertStreamStateOpened(OldSS);
969
970 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
971 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
972 SValBuilder &SVB = C.getSValBuilder();
973 auto &ACtx = C.getASTContext();
974 auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ACtx.IntTy),
975 SVB.getConditionType())
976 .getAs<DefinedOrUnknownSVal>();
977 if (!Cond)
978 return;
979 ProgramStateRef StateNotFailed, StateFailed;
980 std::tie(StateNotFailed, StateFailed) = State->assume(*Cond);
981
982 StateNotFailed =
983 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
984 C.addTransition(StateNotFailed);
985
986 // Add transition for the failed state. The resulting value of the file
987 // position indicator for the stream is indeterminate.
988 StateFailed = StateFailed->set<StreamMap>(
989 StreamSym, StreamState::getOpened(Desc, ErrorFError, true));
990 C.addTransition(StateFailed);
991 }
992
evalFscanf(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const993 void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
994 CheckerContext &C) const {
995 ProgramStateRef State = C.getState();
996 if (Call.getNumArgs() < 2)
997 return;
998 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
999 if (!StreamSym)
1000 return;
1001
1002 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1003 if (!CE)
1004 return;
1005
1006 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
1007 if (!OldSS)
1008 return;
1009
1010 assertStreamStateOpened(OldSS);
1011
1012 SValBuilder &SVB = C.getSValBuilder();
1013 ASTContext &ACtx = C.getASTContext();
1014
1015 // Add the success state.
1016 // In this context "success" means there is not an EOF or other read error
1017 // before any item is matched in 'fscanf'. But there may be match failure,
1018 // therefore return value can be 0 or greater.
1019 // It is not specified what happens if some items (not all) are matched and
1020 // then EOF or read error happens. Now this case is handled like a "success"
1021 // case, and no error flags are set on the stream. This is probably not
1022 // accurate, and the POSIX documentation does not tell more.
1023 if (OldSS->ErrorState != ErrorFEof) {
1024 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
1025 ProgramStateRef StateNotFailed =
1026 State->BindExpr(CE, C.getLocationContext(), RetVal);
1027 auto RetGeZero =
1028 SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
1029 SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
1030 .getAs<DefinedOrUnknownSVal>();
1031 if (!RetGeZero)
1032 return;
1033 StateNotFailed = StateNotFailed->assume(*RetGeZero, true);
1034
1035 C.addTransition(StateNotFailed);
1036 }
1037
1038 // Add transition for the failed state.
1039 // Error occurs if nothing is matched yet and reading the input fails.
1040 // Error can be EOF, or other error. At "other error" FERROR or 'errno' can
1041 // be set but it is not further specified if all are required to be set.
1042 // Documentation does not mention, but file position will be set to
1043 // indeterminate similarly as at 'fread'.
1044 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
1045 StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
1046 ? ErrorFEof
1047 : ErrorNone | ErrorFEof | ErrorFError;
1048 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
1049 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
1050 if (OldSS->ErrorState != ErrorFEof)
1051 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
1052 else
1053 C.addTransition(StateFailed);
1054 }
1055
evalUngetc(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1056 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
1057 CheckerContext &C) const {
1058 ProgramStateRef State = C.getState();
1059 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1060 if (!StreamSym)
1061 return;
1062
1063 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1064 if (!CE)
1065 return;
1066
1067 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
1068 if (!OldSS)
1069 return;
1070
1071 assertStreamStateOpened(OldSS);
1072
1073 // Generate a transition for the success state.
1074 std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
1075 if (!PutVal)
1076 return;
1077 ProgramStateRef StateNotFailed =
1078 State->BindExpr(CE, C.getLocationContext(), *PutVal);
1079 StateNotFailed =
1080 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
1081 C.addTransition(StateNotFailed);
1082
1083 // Add transition for the failed state.
1084 // Failure of 'ungetc' does not result in feof or ferror state.
1085 // If the PutVal has value of EofVal the function should "fail", but this is
1086 // the same transition as the success state.
1087 // In this case only one state transition is added by the analyzer (the two
1088 // new states may be similar).
1089 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
1090 StateFailed =
1091 StateFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
1092 C.addTransition(StateFailed);
1093 }
1094
evalGetdelim(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1095 void StreamChecker::evalGetdelim(const FnDescription *Desc,
1096 const CallEvent &Call,
1097 CheckerContext &C) const {
1098 ProgramStateRef State = C.getState();
1099 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1100 if (!StreamSym)
1101 return;
1102
1103 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1104 if (!CE)
1105 return;
1106
1107 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
1108 if (!OldSS)
1109 return;
1110
1111 assertStreamStateOpened(OldSS);
1112
1113 // Upon successful completion, the getline() and getdelim() functions shall
1114 // return the number of bytes written into the buffer.
1115 // If the end-of-file indicator for the stream is set, the function shall
1116 // return -1.
1117 // If an error occurs, the function shall return -1 and set 'errno'.
1118
1119 // Add transition for the successful state.
1120 if (OldSS->ErrorState != ErrorFEof) {
1121 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
1122 ProgramStateRef StateNotFailed =
1123 State->BindExpr(CE, C.getLocationContext(), RetVal);
1124 SValBuilder &SVB = C.getSValBuilder();
1125 auto Cond =
1126 SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(CE->getType()),
1127 SVB.getConditionType())
1128 .getAs<DefinedOrUnknownSVal>();
1129 if (!Cond)
1130 return;
1131 StateNotFailed = StateNotFailed->assume(*Cond, true);
1132 if (!StateNotFailed)
1133 return;
1134 C.addTransition(StateNotFailed);
1135 }
1136
1137 // Add transition for the failed state.
1138 // If a (non-EOF) error occurs, the resulting value of the file position
1139 // indicator for the stream is indeterminate.
1140 ProgramStateRef StateFailed = bindInt(-1, State, C, CE);
1141 StreamErrorState NewES =
1142 OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError;
1143 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
1144 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
1145 if (OldSS->ErrorState != ErrorFEof)
1146 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
1147 else
1148 C.addTransition(StateFailed);
1149 }
1150
preFseek(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1151 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
1152 CheckerContext &C) const {
1153 ProgramStateRef State = C.getState();
1154 SVal StreamVal = getStreamArg(Desc, Call);
1155 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1156 State);
1157 if (!State)
1158 return;
1159 State = ensureStreamOpened(StreamVal, C, State);
1160 if (!State)
1161 return;
1162 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
1163 if (!State)
1164 return;
1165
1166 C.addTransition(State);
1167 }
1168
evalFseek(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1169 void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
1170 CheckerContext &C) const {
1171 ProgramStateRef State = C.getState();
1172 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1173 if (!StreamSym)
1174 return;
1175
1176 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1177 if (!CE)
1178 return;
1179
1180 // Ignore the call if the stream is not tracked.
1181 if (!State->get<StreamMap>(StreamSym))
1182 return;
1183
1184 const llvm::APSInt *PosV =
1185 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1));
1186 const llvm::APSInt *WhenceV =
1187 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2));
1188
1189 DefinedSVal RetVal = makeRetVal(C, CE);
1190
1191 // Make expression result.
1192 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
1193
1194 // Bifurcate the state into failed and non-failed.
1195 // Return zero on success, nonzero on error.
1196 ProgramStateRef StateNotFailed, StateFailed;
1197 std::tie(StateFailed, StateNotFailed) =
1198 C.getConstraintManager().assumeDual(State, RetVal);
1199
1200 // Reset the state to opened with no error.
1201 StateNotFailed =
1202 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
1203 // We get error.
1204 // It is possible that fseek fails but sets none of the error flags.
1205 // If fseek failed, assume that the file position becomes indeterminate in any
1206 // case.
1207 StreamErrorState NewErrS = ErrorNone | ErrorFError;
1208 // Setting the position to start of file never produces EOF error.
1209 if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal))
1210 NewErrS = NewErrS | ErrorFEof;
1211 StateFailed = StateFailed->set<StreamMap>(
1212 StreamSym, StreamState::getOpened(Desc, NewErrS, true));
1213
1214 C.addTransition(StateNotFailed);
1215 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
1216 }
1217
evalFgetpos(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1218 void StreamChecker::evalFgetpos(const FnDescription *Desc,
1219 const CallEvent &Call,
1220 CheckerContext &C) const {
1221 ProgramStateRef State = C.getState();
1222 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
1223 if (!Sym)
1224 return;
1225
1226 // Do not evaluate if stream is not found.
1227 if (!State->get<StreamMap>(Sym))
1228 return;
1229
1230 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1231 if (!CE)
1232 return;
1233
1234 DefinedSVal RetVal = makeRetVal(C, CE);
1235 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
1236 ProgramStateRef StateNotFailed, StateFailed;
1237 std::tie(StateFailed, StateNotFailed) =
1238 C.getConstraintManager().assumeDual(State, RetVal);
1239
1240 // This function does not affect the stream state.
1241 // Still we add success and failure state with the appropriate return value.
1242 // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
1243 C.addTransition(StateNotFailed);
1244 C.addTransition(StateFailed);
1245 }
1246
evalFsetpos(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1247 void StreamChecker::evalFsetpos(const FnDescription *Desc,
1248 const CallEvent &Call,
1249 CheckerContext &C) const {
1250 ProgramStateRef State = C.getState();
1251 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1252 if (!StreamSym)
1253 return;
1254
1255 const StreamState *SS = State->get<StreamMap>(StreamSym);
1256 if (!SS)
1257 return;
1258
1259 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1260 if (!CE)
1261 return;
1262
1263 assertStreamStateOpened(SS);
1264
1265 DefinedSVal RetVal = makeRetVal(C, CE);
1266 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
1267 ProgramStateRef StateNotFailed, StateFailed;
1268 std::tie(StateFailed, StateNotFailed) =
1269 C.getConstraintManager().assumeDual(State, RetVal);
1270
1271 StateNotFailed = StateNotFailed->set<StreamMap>(
1272 StreamSym, StreamState::getOpened(Desc, ErrorNone, false));
1273
1274 // At failure ferror could be set.
1275 // The standards do not tell what happens with the file position at failure.
1276 // But we can assume that it is dangerous to make a next I/O operation after
1277 // the position was not set correctly (similar to 'fseek').
1278 StateFailed = StateFailed->set<StreamMap>(
1279 StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
1280
1281 C.addTransition(StateNotFailed);
1282 C.addTransition(StateFailed);
1283 }
1284
evalFtell(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1285 void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,
1286 CheckerContext &C) const {
1287 ProgramStateRef State = C.getState();
1288 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
1289 if (!Sym)
1290 return;
1291
1292 if (!State->get<StreamMap>(Sym))
1293 return;
1294
1295 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1296 if (!CE)
1297 return;
1298
1299 SValBuilder &SVB = C.getSValBuilder();
1300 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
1301 ProgramStateRef StateNotFailed =
1302 State->BindExpr(CE, C.getLocationContext(), RetVal);
1303 auto Cond =
1304 SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(Call.getResultType()),
1305 SVB.getConditionType())
1306 .getAs<DefinedOrUnknownSVal>();
1307 if (!Cond)
1308 return;
1309 StateNotFailed = StateNotFailed->assume(*Cond, true);
1310 if (!StateNotFailed)
1311 return;
1312
1313 ProgramStateRef StateFailed = State->BindExpr(
1314 CE, C.getLocationContext(), SVB.makeIntVal(-1, Call.getResultType()));
1315
1316 // This function does not affect the stream state.
1317 // Still we add success and failure state with the appropriate return value.
1318 // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
1319 C.addTransition(StateNotFailed);
1320 C.addTransition(StateFailed);
1321 }
1322
evalRewind(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1323 void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,
1324 CheckerContext &C) const {
1325 ProgramStateRef State = C.getState();
1326 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1327 if (!StreamSym)
1328 return;
1329
1330 const StreamState *SS = State->get<StreamMap>(StreamSym);
1331 if (!SS)
1332 return;
1333
1334 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1335 if (!CE)
1336 return;
1337
1338 assertStreamStateOpened(SS);
1339
1340 State = State->set<StreamMap>(StreamSym,
1341 StreamState::getOpened(Desc, ErrorNone, false));
1342
1343 C.addTransition(State);
1344 }
1345
evalClearerr(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1346 void StreamChecker::evalClearerr(const FnDescription *Desc,
1347 const CallEvent &Call,
1348 CheckerContext &C) const {
1349 ProgramStateRef State = C.getState();
1350 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1351 if (!StreamSym)
1352 return;
1353
1354 const StreamState *SS = State->get<StreamMap>(StreamSym);
1355 if (!SS)
1356 return;
1357
1358 assertStreamStateOpened(SS);
1359
1360 // FilePositionIndeterminate is not cleared.
1361 State = State->set<StreamMap>(
1362 StreamSym,
1363 StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate));
1364 C.addTransition(State);
1365 }
1366
evalFeofFerror(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C,const StreamErrorState & ErrorKind) const1367 void StreamChecker::evalFeofFerror(const FnDescription *Desc,
1368 const CallEvent &Call, CheckerContext &C,
1369 const StreamErrorState &ErrorKind) const {
1370 ProgramStateRef State = C.getState();
1371 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1372 if (!StreamSym)
1373 return;
1374
1375 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1376 if (!CE)
1377 return;
1378
1379 const StreamState *SS = State->get<StreamMap>(StreamSym);
1380 if (!SS)
1381 return;
1382
1383 assertStreamStateOpened(SS);
1384
1385 if (SS->ErrorState & ErrorKind) {
1386 // Execution path with error of ErrorKind.
1387 // Function returns true.
1388 // From now on it is the only one error state.
1389 ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE);
1390 C.addTransition(TrueState->set<StreamMap>(
1391 StreamSym, StreamState::getOpened(Desc, ErrorKind,
1392 SS->FilePositionIndeterminate &&
1393 !ErrorKind.isFEof())));
1394 }
1395 if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) {
1396 // Execution path(s) with ErrorKind not set.
1397 // Function returns false.
1398 // New error state is everything before minus ErrorKind.
1399 ProgramStateRef FalseState = bindInt(0, State, C, CE);
1400 C.addTransition(FalseState->set<StreamMap>(
1401 StreamSym,
1402 StreamState::getOpened(
1403 Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof())));
1404 }
1405 }
1406
preDefault(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1407 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
1408 CheckerContext &C) const {
1409 ProgramStateRef State = C.getState();
1410 SVal StreamVal = getStreamArg(Desc, Call);
1411 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1412 State);
1413 if (!State)
1414 return;
1415 State = ensureStreamOpened(StreamVal, C, State);
1416 if (!State)
1417 return;
1418
1419 C.addTransition(State);
1420 }
1421
evalSetFeofFerror(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C,const StreamErrorState & ErrorKind) const1422 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
1423 const CallEvent &Call, CheckerContext &C,
1424 const StreamErrorState &ErrorKind) const {
1425 ProgramStateRef State = C.getState();
1426 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1427 assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
1428 const StreamState *SS = State->get<StreamMap>(StreamSym);
1429 assert(SS && "Stream should be tracked by the checker.");
1430 State = State->set<StreamMap>(
1431 StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind));
1432 C.addTransition(State);
1433 }
1434
preFflush(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1435 void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call,
1436 CheckerContext &C) const {
1437 ProgramStateRef State = C.getState();
1438 SVal StreamVal = getStreamArg(Desc, Call);
1439 std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
1440 if (!Stream)
1441 return;
1442
1443 ProgramStateRef StateNotNull, StateNull;
1444 std::tie(StateNotNull, StateNull) =
1445 C.getConstraintManager().assumeDual(State, *Stream);
1446 if (StateNotNull && !StateNull)
1447 ensureStreamOpened(StreamVal, C, StateNotNull);
1448 }
1449
evalFflush(const FnDescription * Desc,const CallEvent & Call,CheckerContext & C) const1450 void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call,
1451 CheckerContext &C) const {
1452 ProgramStateRef State = C.getState();
1453 SVal StreamVal = getStreamArg(Desc, Call);
1454 std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
1455 if (!Stream)
1456 return;
1457
1458 // Skip if the stream can be both NULL and non-NULL.
1459 ProgramStateRef StateNotNull, StateNull;
1460 std::tie(StateNotNull, StateNull) =
1461 C.getConstraintManager().assumeDual(State, *Stream);
1462 if (StateNotNull && StateNull)
1463 return;
1464 if (StateNotNull && !StateNull)
1465 State = StateNotNull;
1466 else
1467 State = StateNull;
1468
1469 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1470 if (!CE)
1471 return;
1472
1473 // `fflush` returns EOF on failure, otherwise returns 0.
1474 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
1475 ProgramStateRef StateNotFailed = bindInt(0, State, C, CE);
1476
1477 // Clear error states if `fflush` returns 0, but retain their EOF flags.
1478 auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym,
1479 const StreamState *SS) {
1480 if (SS->ErrorState & ErrorFError) {
1481 StreamErrorState NewES =
1482 (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone;
1483 StreamState NewSS = StreamState::getOpened(Desc, NewES, false);
1484 StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS);
1485 }
1486 };
1487
1488 if (StateNotNull && !StateNull) {
1489 // Skip if the input stream's state is unknown, open-failed or closed.
1490 if (SymbolRef StreamSym = StreamVal.getAsSymbol()) {
1491 const StreamState *SS = State->get<StreamMap>(StreamSym);
1492 if (SS) {
1493 assert(SS->isOpened() && "Stream is expected to be opened");
1494 ClearErrorInNotFailed(StreamSym, SS);
1495 } else
1496 return;
1497 }
1498 } else {
1499 // Clear error states for all streams.
1500 const StreamMapTy &Map = StateNotFailed->get<StreamMap>();
1501 for (const auto &I : Map) {
1502 SymbolRef Sym = I.first;
1503 const StreamState &SS = I.second;
1504 if (SS.isOpened())
1505 ClearErrorInNotFailed(Sym, &SS);
1506 }
1507 }
1508
1509 C.addTransition(StateNotFailed);
1510 C.addTransition(StateFailed);
1511 }
1512
1513 ProgramStateRef
ensureStreamNonNull(SVal StreamVal,const Expr * StreamE,CheckerContext & C,ProgramStateRef State) const1514 StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
1515 CheckerContext &C,
1516 ProgramStateRef State) const {
1517 auto Stream = StreamVal.getAs<DefinedSVal>();
1518 if (!Stream)
1519 return State;
1520
1521 ConstraintManager &CM = C.getConstraintManager();
1522
1523 ProgramStateRef StateNotNull, StateNull;
1524 std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream);
1525
1526 if (!StateNotNull && StateNull) {
1527 if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
1528 auto R = std::make_unique<PathSensitiveBugReport>(
1529 BT_FileNull, "Stream pointer might be NULL.", N);
1530 if (StreamE)
1531 bugreporter::trackExpressionValue(N, StreamE, *R);
1532 C.emitReport(std::move(R));
1533 }
1534 return nullptr;
1535 }
1536
1537 return StateNotNull;
1538 }
1539
ensureStreamOpened(SVal StreamVal,CheckerContext & C,ProgramStateRef State) const1540 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
1541 CheckerContext &C,
1542 ProgramStateRef State) const {
1543 SymbolRef Sym = StreamVal.getAsSymbol();
1544 if (!Sym)
1545 return State;
1546
1547 const StreamState *SS = State->get<StreamMap>(Sym);
1548 if (!SS)
1549 return State;
1550
1551 if (SS->isClosed()) {
1552 // Using a stream pointer after 'fclose' causes undefined behavior
1553 // according to cppreference.com .
1554 ExplodedNode *N = C.generateErrorNode();
1555 if (N) {
1556 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1557 BT_UseAfterClose,
1558 "Stream might be already closed. Causes undefined behaviour.", N));
1559 return nullptr;
1560 }
1561
1562 return State;
1563 }
1564
1565 if (SS->isOpenFailed()) {
1566 // Using a stream that has failed to open is likely to cause problems.
1567 // This should usually not occur because stream pointer is NULL.
1568 // But freopen can cause a state when stream pointer remains non-null but
1569 // failed to open.
1570 ExplodedNode *N = C.generateErrorNode();
1571 if (N) {
1572 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1573 BT_UseAfterOpenFailed,
1574 "Stream might be invalid after "
1575 "(re-)opening it has failed. "
1576 "Can cause undefined behaviour.",
1577 N));
1578 return nullptr;
1579 }
1580 }
1581
1582 return State;
1583 }
1584
ensureNoFilePositionIndeterminate(SVal StreamVal,CheckerContext & C,ProgramStateRef State) const1585 ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate(
1586 SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {
1587 static const char *BugMessage =
1588 "File position of the stream might be 'indeterminate' "
1589 "after a failed operation. "
1590 "Can cause undefined behavior.";
1591
1592 SymbolRef Sym = StreamVal.getAsSymbol();
1593 if (!Sym)
1594 return State;
1595
1596 const StreamState *SS = State->get<StreamMap>(Sym);
1597 if (!SS)
1598 return State;
1599
1600 assert(SS->isOpened() && "First ensure that stream is opened.");
1601
1602 if (SS->FilePositionIndeterminate) {
1603 if (SS->ErrorState & ErrorFEof) {
1604 // The error is unknown but may be FEOF.
1605 // Continue analysis with the FEOF error state.
1606 // Report warning because the other possible error states.
1607 ExplodedNode *N = C.generateNonFatalErrorNode(State);
1608 if (!N)
1609 return nullptr;
1610
1611 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1612 BT_IndeterminatePosition, BugMessage, N));
1613 return State->set<StreamMap>(
1614 Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false));
1615 }
1616
1617 // Known or unknown error state without FEOF possible.
1618 // Stop analysis, report error.
1619 ExplodedNode *N = C.generateErrorNode(State);
1620 if (N)
1621 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1622 BT_IndeterminatePosition, BugMessage, N));
1623
1624 return nullptr;
1625 }
1626
1627 return State;
1628 }
1629
1630 ProgramStateRef
ensureFseekWhenceCorrect(SVal WhenceVal,CheckerContext & C,ProgramStateRef State) const1631 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
1632 ProgramStateRef State) const {
1633 std::optional<nonloc::ConcreteInt> CI =
1634 WhenceVal.getAs<nonloc::ConcreteInt>();
1635 if (!CI)
1636 return State;
1637
1638 int64_t X = CI->getValue().getSExtValue();
1639 if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal)
1640 return State;
1641
1642 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1643 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1644 BT_IllegalWhence,
1645 "The whence argument to fseek() should be "
1646 "SEEK_SET, SEEK_END, or SEEK_CUR.",
1647 N));
1648 return nullptr;
1649 }
1650
1651 return State;
1652 }
1653
reportFEofWarning(SymbolRef StreamSym,CheckerContext & C,ProgramStateRef State) const1654 void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
1655 ProgramStateRef State) const {
1656 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1657 auto R = std::make_unique<PathSensitiveBugReport>(
1658 BT_StreamEof,
1659 "Read function called when stream is in EOF state. "
1660 "Function has no effect.",
1661 N);
1662 R->markInteresting(StreamSym);
1663 C.emitReport(std::move(R));
1664 return;
1665 }
1666 C.addTransition(State);
1667 }
1668
1669 ExplodedNode *
reportLeaks(const SmallVector<SymbolRef,2> & LeakedSyms,CheckerContext & C,ExplodedNode * Pred) const1670 StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
1671 CheckerContext &C, ExplodedNode *Pred) const {
1672 ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred);
1673 if (!Err)
1674 return Pred;
1675
1676 for (SymbolRef LeakSym : LeakedSyms) {
1677 // Resource leaks can result in multiple warning that describe the same kind
1678 // of programming error:
1679 // void f() {
1680 // FILE *F = fopen("a.txt");
1681 // if (rand()) // state split
1682 // return; // warning
1683 // } // warning
1684 // While this isn't necessarily true (leaking the same stream could result
1685 // from a different kinds of errors), the reduction in redundant reports
1686 // makes this a worthwhile heuristic.
1687 // FIXME: Add a checker option to turn this uniqueing feature off.
1688 const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C);
1689 assert(StreamOpenNode && "Could not find place of stream opening.");
1690
1691 PathDiagnosticLocation LocUsedForUniqueing;
1692 if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics())
1693 LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
1694 StreamStmt, C.getSourceManager(),
1695 StreamOpenNode->getLocationContext());
1696
1697 std::unique_ptr<PathSensitiveBugReport> R =
1698 std::make_unique<PathSensitiveBugReport>(
1699 BT_ResourceLeak,
1700 "Opened stream never closed. Potential resource leak.", Err,
1701 LocUsedForUniqueing,
1702 StreamOpenNode->getLocationContext()->getDecl());
1703 R->markInteresting(LeakSym);
1704 C.emitReport(std::move(R));
1705 }
1706
1707 return Err;
1708 }
1709
checkDeadSymbols(SymbolReaper & SymReaper,CheckerContext & C) const1710 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1711 CheckerContext &C) const {
1712 ProgramStateRef State = C.getState();
1713
1714 llvm::SmallVector<SymbolRef, 2> LeakedSyms;
1715
1716 const StreamMapTy &Map = State->get<StreamMap>();
1717 for (const auto &I : Map) {
1718 SymbolRef Sym = I.first;
1719 const StreamState &SS = I.second;
1720 if (!SymReaper.isDead(Sym))
1721 continue;
1722 if (SS.isOpened())
1723 LeakedSyms.push_back(Sym);
1724 State = State->remove<StreamMap>(Sym);
1725 }
1726
1727 ExplodedNode *N = C.getPredecessor();
1728 if (!LeakedSyms.empty())
1729 N = reportLeaks(LeakedSyms, C, N);
1730
1731 C.addTransition(State, N);
1732 }
1733
checkPointerEscape(ProgramStateRef State,const InvalidatedSymbols & Escaped,const CallEvent * Call,PointerEscapeKind Kind) const1734 ProgramStateRef StreamChecker::checkPointerEscape(
1735 ProgramStateRef State, const InvalidatedSymbols &Escaped,
1736 const CallEvent *Call, PointerEscapeKind Kind) const {
1737 // Check for file-handling system call that is not handled by the checker.
1738 // FIXME: The checker should be updated to handle all system calls that take
1739 // 'FILE*' argument. These are now ignored.
1740 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader())
1741 return State;
1742
1743 for (SymbolRef Sym : Escaped) {
1744 // The symbol escaped.
1745 // From now the stream can be manipulated in unknown way to the checker,
1746 // it is not possible to handle it any more.
1747 // Optimistically, assume that the corresponding file handle will be closed
1748 // somewhere else.
1749 // Remove symbol from state so the following stream calls on this symbol are
1750 // not handled by the checker.
1751 State = State->remove<StreamMap>(Sym);
1752 }
1753 return State;
1754 }
1755
1756 //===----------------------------------------------------------------------===//
1757 // Checker registration.
1758 //===----------------------------------------------------------------------===//
1759
registerStreamChecker(CheckerManager & Mgr)1760 void ento::registerStreamChecker(CheckerManager &Mgr) {
1761 Mgr.registerChecker<StreamChecker>();
1762 }
1763
shouldRegisterStreamChecker(const CheckerManager & Mgr)1764 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {
1765 return true;
1766 }
1767
registerStreamTesterChecker(CheckerManager & Mgr)1768 void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
1769 auto *Checker = Mgr.getChecker<StreamChecker>();
1770 Checker->TestMode = true;
1771 }
1772
shouldRegisterStreamTesterChecker(const CheckerManager & Mgr)1773 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
1774 return true;
1775 }
1776