clang 18.0.0git
StreamChecker.cpp
Go to the documentation of this file.
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
24#include <functional>
25#include <optional>
26
27using namespace clang;
28using namespace ento;
29using namespace std::placeholders;
30
31//===----------------------------------------------------------------------===//
32// Definition of state data structures.
33//===----------------------------------------------------------------------===//
34
35namespace {
36
37struct 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.
45struct StreamErrorState {
46 /// The stream can be in state where none of the error flags set.
47 bool NoError = true;
48 /// The stream can be in state where the EOF indicator is set.
49 bool FEof = false;
50 /// The stream can be in state where the error indicator is set.
51 bool FError = false;
52
53 bool isNoError() const { return NoError && !FEof && !FError; }
54 bool isFEof() const { return !NoError && FEof && !FError; }
55 bool isFError() const { return !NoError && !FEof && FError; }
56
57 bool operator==(const StreamErrorState &ES) const {
58 return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError;
59 }
60
61 bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); }
62
63 StreamErrorState operator|(const StreamErrorState &E) const {
64 return {NoError || E.NoError, FEof || E.FEof, FError || E.FError};
65 }
66
67 StreamErrorState operator&(const StreamErrorState &E) const {
68 return {NoError && E.NoError, FEof && E.FEof, FError && E.FError};
69 }
70
71 StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; }
72
73 /// Returns if the StreamErrorState is a valid object.
74 operator bool() const { return NoError || FEof || FError; }
75
76 void Profile(llvm::FoldingSetNodeID &ID) const {
77 ID.AddBoolean(NoError);
78 ID.AddBoolean(FEof);
79 ID.AddBoolean(FError);
80 }
81};
82
83const StreamErrorState ErrorNone{true, false, false};
84const StreamErrorState ErrorFEof{false, true, false};
85const StreamErrorState ErrorFError{false, false, true};
86
87/// Full state information about a stream pointer.
88struct StreamState {
89 /// The last file operation called in the stream.
90 /// Can be nullptr.
91 const FnDescription *LastOperation;
92
93 /// State of a stream symbol.
94 enum KindTy {
95 Opened, /// Stream is opened.
96 Closed, /// Closed stream (an invalid stream pointer after it was closed).
97 OpenFailed /// The last open operation has failed.
98 } State;
99
100 /// State of the error flags.
101 /// Ignored in non-opened stream state but must be NoError.
102 StreamErrorState const ErrorState;
103
104 /// Indicate if the file has an "indeterminate file position indicator".
105 /// This can be set at a failing read or write or seek operation.
106 /// If it is set no more read or write is allowed.
107 /// This value is not dependent on the stream error flags:
108 /// The error flag may be cleared with `clearerr` but the file position
109 /// remains still indeterminate.
110 /// This value applies to all error states in ErrorState except FEOF.
111 /// An EOF+indeterminate state is the same as EOF state.
112 bool const FilePositionIndeterminate = false;
113
114 StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES,
115 bool IsFilePositionIndeterminate)
116 : LastOperation(L), State(S), ErrorState(ES),
117 FilePositionIndeterminate(IsFilePositionIndeterminate) {
118 assert((!ES.isFEof() || !IsFilePositionIndeterminate) &&
119 "FilePositionIndeterminate should be false in FEof case.");
120 assert((State == Opened || ErrorState.isNoError()) &&
121 "ErrorState should be None in non-opened stream state.");
122 }
123
124 bool isOpened() const { return State == Opened; }
125 bool isClosed() const { return State == Closed; }
126 bool isOpenFailed() const { return State == OpenFailed; }
127
128 bool operator==(const StreamState &X) const {
129 // In not opened state error state should always NoError, so comparison
130 // here is no problem.
131 return LastOperation == X.LastOperation && State == X.State &&
132 ErrorState == X.ErrorState &&
133 FilePositionIndeterminate == X.FilePositionIndeterminate;
134 }
135
136 static StreamState getOpened(const FnDescription *L,
137 const StreamErrorState &ES = ErrorNone,
138 bool IsFilePositionIndeterminate = false) {
139 return StreamState{L, Opened, ES, IsFilePositionIndeterminate};
140 }
141 static StreamState getClosed(const FnDescription *L) {
142 return StreamState{L, Closed, {}, false};
143 }
144 static StreamState getOpenFailed(const FnDescription *L) {
145 return StreamState{L, OpenFailed, {}, false};
146 }
147
148 void Profile(llvm::FoldingSetNodeID &ID) const {
149 ID.AddPointer(LastOperation);
150 ID.AddInteger(State);
151 ErrorState.Profile(ID);
152 ID.AddBoolean(FilePositionIndeterminate);
153 }
154};
155
156} // namespace
157
158//===----------------------------------------------------------------------===//
159// StreamChecker class and utility functions.
160//===----------------------------------------------------------------------===//
161
162namespace {
163
164class StreamChecker;
165using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
166 const CallEvent &, CheckerContext &)>;
167
168using ArgNoTy = unsigned int;
169static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();
170
171struct 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.
179SVal 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.
186DefinedSVal 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
195ProgramStateRef 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
204ProgramStateRef 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
211class 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
225public:
226 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
227 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
228 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
229 ProgramStateRef checkPointerEscape(ProgramStateRef State,
230 const InvalidatedSymbols &Escaped,
231 const CallEvent *Call,
232 PointerEscapeKind Kind) const;
233
234 /// If true, evaluate special testing stream functions.
235 bool TestMode = false;
236
237 const BugType *getBT_StreamEof() const { return &BT_StreamEof; }
238
239private:
240 CallDescriptionMap<FnDescription> FnDescriptions = {
241 {{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
242 {{{"freopen"}, 3},
243 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
244 {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
245 {{{"fclose"}, 1},
246 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
247 {{{"fread"}, 4},
248 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
249 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},
250 {{{"fwrite"}, 4},
251 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
252 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},
253 {{{"fgetc"}, 1},
254 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
255 std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
256 {{{"fgets"}, 3},
257 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
258 std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},
259 {{{"fputc"}, 2},
260 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
261 std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
262 {{{"fputs"}, 2},
263 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
264 std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
265 {{{"fseek"}, 3},
266 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
267 {{{"ftell"}, 1},
268 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
269 {{{"rewind"}, 1},
270 {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
271 {{{"fgetpos"}, 2},
272 {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}},
273 {{{"fsetpos"}, 2},
274 {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},
275 {{{"clearerr"}, 1},
276 {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
277 {{{"feof"}, 1},
278 {&StreamChecker::preDefault,
279 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof),
280 0}},
281 {{{"ferror"}, 1},
282 {&StreamChecker::preDefault,
283 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError),
284 0}},
285 {{{"fileno"}, 1}, {&StreamChecker::preDefault, nullptr, 0}},
286 };
287
288 CallDescriptionMap<FnDescription> FnTestDescriptions = {
289 {{{"StreamTesterChecker_make_feof_stream"}, 1},
290 {nullptr,
291 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof),
292 0}},
293 {{{"StreamTesterChecker_make_ferror_stream"}, 1},
294 {nullptr,
295 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
296 ErrorFError),
297 0}},
298 };
299
300 /// Expanded value of EOF, empty before initialization.
301 mutable std::optional<int> EofVal;
302 /// Expanded value of SEEK_SET, 0 if not found.
303 mutable int SeekSetVal = 0;
304 /// Expanded value of SEEK_CUR, 1 if not found.
305 mutable int SeekCurVal = 1;
306 /// Expanded value of SEEK_END, 2 if not found.
307 mutable int SeekEndVal = 2;
308
309 void evalFopen(const FnDescription *Desc, const CallEvent &Call,
310 CheckerContext &C) const;
311
312 void preFreopen(const FnDescription *Desc, const CallEvent &Call,
313 CheckerContext &C) const;
314 void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
315 CheckerContext &C) const;
316
317 void evalFclose(const FnDescription *Desc, const CallEvent &Call,
318 CheckerContext &C) const;
319
320 void preReadWrite(const FnDescription *Desc, const CallEvent &Call,
321 CheckerContext &C, bool IsRead) const;
322
323 void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
324 CheckerContext &C, bool IsFread) const;
325
326 void evalFgetx(const FnDescription *Desc, const CallEvent &Call,
327 CheckerContext &C, bool SingleChar) const;
328
329 void evalFputx(const FnDescription *Desc, const CallEvent &Call,
330 CheckerContext &C, bool IsSingleChar) const;
331
332 void preFseek(const FnDescription *Desc, const CallEvent &Call,
333 CheckerContext &C) const;
334 void evalFseek(const FnDescription *Desc, const CallEvent &Call,
335 CheckerContext &C) const;
336
337 void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,
338 CheckerContext &C) const;
339
340 void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,
341 CheckerContext &C) const;
342
343 void evalFtell(const FnDescription *Desc, const CallEvent &Call,
344 CheckerContext &C) const;
345
346 void evalRewind(const FnDescription *Desc, const CallEvent &Call,
347 CheckerContext &C) const;
348
349 void preDefault(const FnDescription *Desc, const CallEvent &Call,
350 CheckerContext &C) const;
351
352 void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
353 CheckerContext &C) const;
354
355 void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
357 const StreamErrorState &ErrorKind) const;
358
359 void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
361 const StreamErrorState &ErrorKind) const;
362
363 /// Check that the stream (in StreamVal) is not NULL.
364 /// If it can only be NULL a fatal error is emitted and nullptr returned.
365 /// Otherwise the return value is a new state where the stream is constrained
366 /// to be non-null.
367 ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
369 ProgramStateRef State) const;
370
371 /// Check that the stream is the opened state.
372 /// If the stream is known to be not opened an error is generated
373 /// and nullptr returned, otherwise the original state is returned.
374 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
375 ProgramStateRef State) const;
376
377 /// Check that the stream has not an invalid ("indeterminate") file position,
378 /// generate warning for it.
379 /// (EOF is not an invalid position.)
380 /// The returned state can be nullptr if a fatal error was generated.
381 /// It can return non-null state if the stream has not an invalid position or
382 /// there is execution path with non-invalid position.
384 ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C,
385 ProgramStateRef State) const;
386
387 /// Check the legality of the 'whence' argument of 'fseek'.
388 /// Generate error and return nullptr if it is found to be illegal.
389 /// Otherwise returns the state.
390 /// (State is not changed here because the "whence" value is already known.)
391 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
392 ProgramStateRef State) const;
393
394 /// Generate warning about stream in EOF state.
395 /// There will be always a state transition into the passed State,
396 /// by the new non-fatal error node or (if failed) a normal transition,
397 /// to ensure uniform handling.
398 void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
399 ProgramStateRef State) const;
400
401 /// Emit resource leak warnings for the given symbols.
402 /// Createn a non-fatal error node for these, and returns it (if any warnings
403 /// were generated). Return value is non-null.
404 ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
405 CheckerContext &C, ExplodedNode *Pred) const;
406
407 /// Find the description data of the function called by a call event.
408 /// Returns nullptr if no function is recognized.
409 const FnDescription *lookupFn(const CallEvent &Call) const {
410 // Recognize "global C functions" with only integral or pointer arguments
411 // (and matching name) as stream functions.
412 if (!Call.isGlobalCFunction())
413 return nullptr;
414 for (auto *P : Call.parameters()) {
415 QualType T = P->getType();
417 return nullptr;
418 }
419
420 return FnDescriptions.lookup(Call);
421 }
422
423 /// Generate a message for BugReporterVisitor if the stored symbol is
424 /// marked as interesting by the actual bug report.
425 const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym,
426 const std::string &Message) const {
427 return C.getNoteTag([this, StreamSym,
428 Message](PathSensitiveBugReport &BR) -> std::string {
429 if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak)
430 return Message;
431 return "";
432 });
433 }
434
435 const NoteTag *constructSetEofNoteTag(CheckerContext &C,
436 SymbolRef StreamSym) const {
437 return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
438 if (!BR.isInteresting(StreamSym) ||
439 &BR.getBugType() != this->getBT_StreamEof())
440 return "";
441
442 BR.markNotInteresting(StreamSym);
443
444 return "Assuming stream reaches end-of-file here";
445 });
446 }
447
448 void initMacroValues(CheckerContext &C) const {
449 if (EofVal)
450 return;
451
452 if (const std::optional<int> OptInt =
453 tryExpandAsInteger("EOF", C.getPreprocessor()))
454 EofVal = *OptInt;
455 else
456 EofVal = -1;
457 if (const std::optional<int> OptInt =
458 tryExpandAsInteger("SEEK_SET", C.getPreprocessor()))
459 SeekSetVal = *OptInt;
460 if (const std::optional<int> OptInt =
461 tryExpandAsInteger("SEEK_END", C.getPreprocessor()))
462 SeekEndVal = *OptInt;
463 if (const std::optional<int> OptInt =
464 tryExpandAsInteger("SEEK_CUR", C.getPreprocessor()))
465 SeekCurVal = *OptInt;
466 }
467
468 /// Searches for the ExplodedNode where the file descriptor was acquired for
469 /// StreamSym.
470 static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
471 SymbolRef StreamSym,
473};
474
475} // end anonymous namespace
476
477// This map holds the state of a stream.
478// The stream is identified with a SymbolRef that is created when a stream
479// opening function is modeled by the checker.
480REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
481
482inline void assertStreamStateOpened(const StreamState *SS) {
483 assert(SS->isOpened() && "Stream is expected to be opened");
484}
485
486const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,
487 SymbolRef StreamSym,
488 CheckerContext &C) {
489 ProgramStateRef State = N->getState();
490 // When bug type is resource leak, exploded node N may not have state info
491 // for leaked file descriptor, but predecessor should have it.
492 if (!State->get<StreamMap>(StreamSym))
493 N = N->getFirstPred();
494
495 const ExplodedNode *Pred = N;
496 while (N) {
497 State = N->getState();
498 if (!State->get<StreamMap>(StreamSym))
499 return Pred;
500 Pred = N;
501 N = N->getFirstPred();
502 }
503
504 return nullptr;
505}
506
507//===----------------------------------------------------------------------===//
508// Methods of StreamChecker.
509//===----------------------------------------------------------------------===//
510
511void StreamChecker::checkPreCall(const CallEvent &Call,
512 CheckerContext &C) const {
513 initMacroValues(C);
514
515 const FnDescription *Desc = lookupFn(Call);
516 if (!Desc || !Desc->PreFn)
517 return;
518
519 Desc->PreFn(this, Desc, Call, C);
520}
521
522bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
523 const FnDescription *Desc = lookupFn(Call);
524 if (!Desc && TestMode)
525 Desc = FnTestDescriptions.lookup(Call);
526 if (!Desc || !Desc->EvalFn)
527 return false;
528
529 Desc->EvalFn(this, Desc, Call, C);
530
531 return C.isDifferent();
532}
533
534void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
535 CheckerContext &C) const {
536 ProgramStateRef State = C.getState();
537 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
538 if (!CE)
539 return;
540
541 DefinedSVal RetVal = makeRetVal(C, CE);
542 SymbolRef RetSym = RetVal.getAsSymbol();
543 assert(RetSym && "RetVal must be a symbol here.");
544
545 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
546
547 // Bifurcate the state into two: one with a valid FILE* pointer, the other
548 // with a NULL.
549 ProgramStateRef StateNotNull, StateNull;
550 std::tie(StateNotNull, StateNull) =
551 C.getConstraintManager().assumeDual(State, RetVal);
552
553 StateNotNull =
554 StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
555 StateNull =
556 StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
557
558 C.addTransition(StateNotNull,
559 constructNoteTag(C, RetSym, "Stream opened here"));
560 C.addTransition(StateNull);
561}
562
563void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
564 CheckerContext &C) const {
565 // Do not allow NULL as passed stream pointer but allow a closed stream.
566 ProgramStateRef State = C.getState();
567 State = ensureStreamNonNull(getStreamArg(Desc, Call),
568 Call.getArgExpr(Desc->StreamArgNo), C, State);
569 if (!State)
570 return;
571
572 C.addTransition(State);
573}
574
575void StreamChecker::evalFreopen(const FnDescription *Desc,
576 const CallEvent &Call,
577 CheckerContext &C) const {
578 ProgramStateRef State = C.getState();
579
580 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
581 if (!CE)
582 return;
583
584 std::optional<DefinedSVal> StreamVal =
585 getStreamArg(Desc, Call).getAs<DefinedSVal>();
586 if (!StreamVal)
587 return;
588
589 SymbolRef StreamSym = StreamVal->getAsSymbol();
590 // Do not care about concrete values for stream ("(FILE *)0x12345"?).
591 // FIXME: Can be stdin, stdout, stderr such values?
592 if (!StreamSym)
593 return;
594
595 // Do not handle untracked stream. It is probably escaped.
596 if (!State->get<StreamMap>(StreamSym))
597 return;
598
599 // Generate state for non-failed case.
600 // Return value is the passed stream pointer.
601 // According to the documentations, the stream is closed first
602 // but any close error is ignored. The state changes to (or remains) opened.
603 ProgramStateRef StateRetNotNull =
604 State->BindExpr(CE, C.getLocationContext(), *StreamVal);
605 // Generate state for NULL return value.
606 // Stream switches to OpenFailed state.
607 ProgramStateRef StateRetNull =
608 State->BindExpr(CE, C.getLocationContext(),
609 C.getSValBuilder().makeNullWithType(CE->getType()));
610
611 StateRetNotNull =
612 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
613 StateRetNull =
614 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
615
616 C.addTransition(StateRetNotNull,
617 constructNoteTag(C, StreamSym, "Stream reopened here"));
618 C.addTransition(StateRetNull);
619}
620
621void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
622 CheckerContext &C) const {
623 ProgramStateRef State = C.getState();
624 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
625 if (!Sym)
626 return;
627
628 const StreamState *SS = State->get<StreamMap>(Sym);
629 if (!SS)
630 return;
631
632 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
633 if (!CE)
634 return;
635
637
638 // Close the File Descriptor.
639 // Regardless if the close fails or not, stream becomes "closed"
640 // and can not be used any more.
641 State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc));
642
643 // Return 0 on success, EOF on failure.
644 SValBuilder &SVB = C.getSValBuilder();
645 ProgramStateRef StateSuccess = State->BindExpr(
646 CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy));
647 ProgramStateRef StateFailure =
648 State->BindExpr(CE, C.getLocationContext(),
649 SVB.makeIntVal(*EofVal, C.getASTContext().IntTy));
650
651 C.addTransition(StateSuccess);
652 C.addTransition(StateFailure);
653}
654
655void StreamChecker::preReadWrite(const FnDescription *Desc,
657 bool IsRead) const {
658 ProgramStateRef State = C.getState();
659 SVal StreamVal = getStreamArg(Desc, Call);
660 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
661 State);
662 if (!State)
663 return;
664 State = ensureStreamOpened(StreamVal, C, State);
665 if (!State)
666 return;
667 State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
668 if (!State)
669 return;
670
671 if (!IsRead) {
672 C.addTransition(State);
673 return;
674 }
675
676 SymbolRef Sym = StreamVal.getAsSymbol();
677 if (Sym && State->get<StreamMap>(Sym)) {
678 const StreamState *SS = State->get<StreamMap>(Sym);
679 if (SS->ErrorState & ErrorFEof)
680 reportFEofWarning(Sym, C, State);
681 } else {
682 C.addTransition(State);
683 }
684}
685
686void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
688 bool IsFread) const {
689 ProgramStateRef State = C.getState();
690 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
691 if (!StreamSym)
692 return;
693
694 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
695 if (!CE)
696 return;
697
698 std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();
699 if (!SizeVal)
700 return;
701 std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>();
702 if (!NMembVal)
703 return;
704
705 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
706 if (!OldSS)
707 return;
708
710
711 // C'99 standard, ยง7.19.8.1.3, the return value of fread:
712 // The fread function returns the number of elements successfully read, which
713 // may be less than nmemb if a read error or end-of-file is encountered. If
714 // size or nmemb is zero, fread returns zero and the contents of the array and
715 // the state of the stream remain unchanged.
716
717 if (State->isNull(*SizeVal).isConstrainedTrue() ||
718 State->isNull(*NMembVal).isConstrainedTrue()) {
719 // This is the "size or nmemb is zero" case.
720 // Just return 0, do nothing more (not clear the error flags).
721 State = bindInt(0, State, C, CE);
722 C.addTransition(State);
723 return;
724 }
725
726 // Generate a transition for the success state.
727 // If we know the state to be FEOF at fread, do not add a success state.
728 if (!IsFread || (OldSS->ErrorState != ErrorFEof)) {
729 ProgramStateRef StateNotFailed =
730 State->BindExpr(CE, C.getLocationContext(), *NMembVal);
731 StateNotFailed =
732 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
733 C.addTransition(StateNotFailed);
734 }
735
736 // Add transition for the failed state.
737 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
738 ProgramStateRef StateFailed =
739 State->BindExpr(CE, C.getLocationContext(), RetVal);
740 SValBuilder &SVB = C.getSValBuilder();
741 auto Cond =
742 SVB.evalBinOpNN(State, BO_LT, RetVal, *NMembVal, SVB.getConditionType())
743 .getAs<DefinedOrUnknownSVal>();
744 if (!Cond)
745 return;
746 StateFailed = StateFailed->assume(*Cond, true);
747 if (!StateFailed)
748 return;
749
750 StreamErrorState NewES;
751 if (IsFread)
752 NewES =
753 (OldSS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError;
754 else
755 NewES = ErrorFError;
756 // If a (non-EOF) error occurs, the resulting value of the file position
757 // indicator for the stream is indeterminate.
758 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
759 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
760 if (IsFread && OldSS->ErrorState != ErrorFEof)
761 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
762 else
763 C.addTransition(StateFailed);
764}
765
766void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call,
767 CheckerContext &C, bool SingleChar) const {
768 ProgramStateRef State = C.getState();
769 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
770 if (!StreamSym)
771 return;
772
773 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
774 if (!CE)
775 return;
776
777 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
778 if (!OldSS)
779 return;
780
782
783 // `fgetc` returns the read character on success, otherwise returns EOF.
784 // `fgets` returns the read buffer address on success, otherwise returns NULL.
785
786 if (OldSS->ErrorState != ErrorFEof) {
787 if (SingleChar) {
788 // Generate a transition for the success state of `fgetc`.
789 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
790 ProgramStateRef StateNotFailed =
791 State->BindExpr(CE, C.getLocationContext(), RetVal);
792 SValBuilder &SVB = C.getSValBuilder();
793 ASTContext &ASTC = C.getASTContext();
794 // The returned 'unsigned char' of `fgetc` is converted to 'int',
795 // so we need to check if it is in range [0, 255].
796 auto CondLow =
797 SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ASTC.IntTy),
798 SVB.getConditionType())
799 .getAs<DefinedOrUnknownSVal>();
800 auto CondHigh =
801 SVB.evalBinOp(State, BO_LE, RetVal,
804 .getLimitedValue(),
805 ASTC.IntTy),
806 SVB.getConditionType())
807 .getAs<DefinedOrUnknownSVal>();
808 if (!CondLow || !CondHigh)
809 return;
810 StateNotFailed = StateNotFailed->assume(*CondLow, true);
811 if (!StateNotFailed)
812 return;
813 StateNotFailed = StateNotFailed->assume(*CondHigh, true);
814 if (!StateNotFailed)
815 return;
816 C.addTransition(StateNotFailed);
817 } else {
818 // Generate a transition for the success state of `fgets`.
819 std::optional<DefinedSVal> GetBuf =
820 Call.getArgSVal(0).getAs<DefinedSVal>();
821 if (!GetBuf)
822 return;
823 ProgramStateRef StateNotFailed =
824 State->BindExpr(CE, C.getLocationContext(), *GetBuf);
825 StateNotFailed = StateNotFailed->set<StreamMap>(
826 StreamSym, StreamState::getOpened(Desc));
827 C.addTransition(StateNotFailed);
828 }
829 }
830
831 // Add transition for the failed state.
832 ProgramStateRef StateFailed;
833 if (SingleChar)
834 StateFailed = bindInt(*EofVal, State, C, CE);
835 else
836 StateFailed =
837 State->BindExpr(CE, C.getLocationContext(),
838 C.getSValBuilder().makeNullWithType(CE->getType()));
839
840 // If a (non-EOF) error occurs, the resulting value of the file position
841 // indicator for the stream is indeterminate.
842 StreamErrorState NewES =
843 OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError;
844 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
845 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
846 if (OldSS->ErrorState != ErrorFEof)
847 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
848 else
849 C.addTransition(StateFailed);
850}
851
852void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,
853 CheckerContext &C, bool IsSingleChar) const {
854 ProgramStateRef State = C.getState();
855 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
856 if (!StreamSym)
857 return;
858
859 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
860 if (!CE)
861 return;
862
863 const StreamState *OldSS = State->get<StreamMap>(StreamSym);
864 if (!OldSS)
865 return;
866
868
869 // `fputc` returns the written character on success, otherwise returns EOF.
870 // `fputs` returns a non negative value on sucecess, otherwise returns EOF.
871
872 if (IsSingleChar) {
873 // Generate a transition for the success state of `fputc`.
874 std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
875 if (!PutVal)
876 return;
877 ProgramStateRef StateNotFailed =
878 State->BindExpr(CE, C.getLocationContext(), *PutVal);
879 StateNotFailed =
880 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
881 C.addTransition(StateNotFailed);
882 } else {
883 // Generate a transition for the success state of `fputs`.
884 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
885 ProgramStateRef StateNotFailed =
886 State->BindExpr(CE, C.getLocationContext(), RetVal);
887 SValBuilder &SVB = C.getSValBuilder();
888 auto &ASTC = C.getASTContext();
889 auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ASTC.IntTy),
890 SVB.getConditionType())
891 .getAs<DefinedOrUnknownSVal>();
892 if (!Cond)
893 return;
894 StateNotFailed = StateNotFailed->assume(*Cond, true);
895 if (!StateNotFailed)
896 return;
897 StateNotFailed =
898 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
899 C.addTransition(StateNotFailed);
900 }
901
902 // Add transition for the failed state. The resulting value of the file
903 // position indicator for the stream is indeterminate.
904 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
905 StreamState NewSS = StreamState::getOpened(Desc, ErrorFError, true);
906 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
907 C.addTransition(StateFailed);
908}
909
910void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
911 CheckerContext &C) const {
912 ProgramStateRef State = C.getState();
913 SVal StreamVal = getStreamArg(Desc, Call);
914 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
915 State);
916 if (!State)
917 return;
918 State = ensureStreamOpened(StreamVal, C, State);
919 if (!State)
920 return;
921 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
922 if (!State)
923 return;
924
925 C.addTransition(State);
926}
927
928void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
929 CheckerContext &C) const {
930 ProgramStateRef State = C.getState();
931 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
932 if (!StreamSym)
933 return;
934
935 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
936 if (!CE)
937 return;
938
939 // Ignore the call if the stream is not tracked.
940 if (!State->get<StreamMap>(StreamSym))
941 return;
942
943 const llvm::APSInt *PosV =
944 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1));
945 const llvm::APSInt *WhenceV =
946 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2));
947
948 DefinedSVal RetVal = makeRetVal(C, CE);
949
950 // Make expression result.
951 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
952
953 // Bifurcate the state into failed and non-failed.
954 // Return zero on success, nonzero on error.
955 ProgramStateRef StateNotFailed, StateFailed;
956 std::tie(StateFailed, StateNotFailed) =
957 C.getConstraintManager().assumeDual(State, RetVal);
958
959 // Reset the state to opened with no error.
960 StateNotFailed =
961 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
962 // We get error.
963 // It is possible that fseek fails but sets none of the error flags.
964 // If fseek failed, assume that the file position becomes indeterminate in any
965 // case.
966 StreamErrorState NewErrS = ErrorNone | ErrorFError;
967 // Setting the position to start of file never produces EOF error.
968 if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal))
969 NewErrS = NewErrS | ErrorFEof;
970 StateFailed = StateFailed->set<StreamMap>(
971 StreamSym, StreamState::getOpened(Desc, NewErrS, true));
972
973 C.addTransition(StateNotFailed);
974 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
975}
976
977void StreamChecker::evalFgetpos(const FnDescription *Desc,
978 const CallEvent &Call,
979 CheckerContext &C) const {
980 ProgramStateRef State = C.getState();
981 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
982 if (!Sym)
983 return;
984
985 // Do not evaluate if stream is not found.
986 if (!State->get<StreamMap>(Sym))
987 return;
988
989 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
990 if (!CE)
991 return;
992
993 DefinedSVal RetVal = makeRetVal(C, CE);
994 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
995 ProgramStateRef StateNotFailed, StateFailed;
996 std::tie(StateFailed, StateNotFailed) =
997 C.getConstraintManager().assumeDual(State, RetVal);
998
999 // This function does not affect the stream state.
1000 // Still we add success and failure state with the appropriate return value.
1001 // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
1002 C.addTransition(StateNotFailed);
1003 C.addTransition(StateFailed);
1004}
1005
1006void StreamChecker::evalFsetpos(const FnDescription *Desc,
1007 const CallEvent &Call,
1008 CheckerContext &C) const {
1009 ProgramStateRef State = C.getState();
1010 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1011 if (!StreamSym)
1012 return;
1013
1014 const StreamState *SS = State->get<StreamMap>(StreamSym);
1015 if (!SS)
1016 return;
1017
1018 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1019 if (!CE)
1020 return;
1021
1023
1024 DefinedSVal RetVal = makeRetVal(C, CE);
1025 State = State->BindExpr(CE, C.getLocationContext(), RetVal);
1026 ProgramStateRef StateNotFailed, StateFailed;
1027 std::tie(StateFailed, StateNotFailed) =
1028 C.getConstraintManager().assumeDual(State, RetVal);
1029
1030 StateNotFailed = StateNotFailed->set<StreamMap>(
1031 StreamSym, StreamState::getOpened(Desc, ErrorNone, false));
1032
1033 // At failure ferror could be set.
1034 // The standards do not tell what happens with the file position at failure.
1035 // But we can assume that it is dangerous to make a next I/O operation after
1036 // the position was not set correctly (similar to 'fseek').
1037 StateFailed = StateFailed->set<StreamMap>(
1038 StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
1039
1040 C.addTransition(StateNotFailed);
1041 C.addTransition(StateFailed);
1042}
1043
1044void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,
1045 CheckerContext &C) const {
1046 ProgramStateRef State = C.getState();
1047 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
1048 if (!Sym)
1049 return;
1050
1051 if (!State->get<StreamMap>(Sym))
1052 return;
1053
1054 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1055 if (!CE)
1056 return;
1057
1058 SValBuilder &SVB = C.getSValBuilder();
1059 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
1060 ProgramStateRef StateNotFailed =
1061 State->BindExpr(CE, C.getLocationContext(), RetVal);
1062 auto Cond = SVB.evalBinOp(State, BO_GE, RetVal,
1063 SVB.makeZeroVal(C.getASTContext().LongTy),
1064 SVB.getConditionType())
1065 .getAs<DefinedOrUnknownSVal>();
1066 if (!Cond)
1067 return;
1068 StateNotFailed = StateNotFailed->assume(*Cond, true);
1069 if (!StateNotFailed)
1070 return;
1071
1072 ProgramStateRef StateFailed = State->BindExpr(
1073 CE, C.getLocationContext(), SVB.makeIntVal(-1, C.getASTContext().LongTy));
1074
1075 // This function does not affect the stream state.
1076 // Still we add success and failure state with the appropriate return value.
1077 // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
1078 C.addTransition(StateNotFailed);
1079 C.addTransition(StateFailed);
1080}
1081
1082void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,
1083 CheckerContext &C) const {
1084 ProgramStateRef State = C.getState();
1085 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1086 if (!StreamSym)
1087 return;
1088
1089 const StreamState *SS = State->get<StreamMap>(StreamSym);
1090 if (!SS)
1091 return;
1092
1093 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1094 if (!CE)
1095 return;
1096
1098
1099 State = State->set<StreamMap>(StreamSym,
1100 StreamState::getOpened(Desc, ErrorNone, false));
1101
1102 C.addTransition(State);
1103}
1104
1105void StreamChecker::evalClearerr(const FnDescription *Desc,
1106 const CallEvent &Call,
1107 CheckerContext &C) const {
1108 ProgramStateRef State = C.getState();
1109 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1110 if (!StreamSym)
1111 return;
1112
1113 const StreamState *SS = State->get<StreamMap>(StreamSym);
1114 if (!SS)
1115 return;
1116
1118
1119 // FilePositionIndeterminate is not cleared.
1120 State = State->set<StreamMap>(
1121 StreamSym,
1122 StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate));
1123 C.addTransition(State);
1124}
1125
1126void StreamChecker::evalFeofFerror(const FnDescription *Desc,
1127 const CallEvent &Call, CheckerContext &C,
1128 const StreamErrorState &ErrorKind) const {
1129 ProgramStateRef State = C.getState();
1130 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1131 if (!StreamSym)
1132 return;
1133
1134 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1135 if (!CE)
1136 return;
1137
1138 const StreamState *SS = State->get<StreamMap>(StreamSym);
1139 if (!SS)
1140 return;
1141
1143
1144 if (SS->ErrorState & ErrorKind) {
1145 // Execution path with error of ErrorKind.
1146 // Function returns true.
1147 // From now on it is the only one error state.
1148 ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE);
1149 C.addTransition(TrueState->set<StreamMap>(
1150 StreamSym, StreamState::getOpened(Desc, ErrorKind,
1151 SS->FilePositionIndeterminate &&
1152 !ErrorKind.isFEof())));
1153 }
1154 if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) {
1155 // Execution path(s) with ErrorKind not set.
1156 // Function returns false.
1157 // New error state is everything before minus ErrorKind.
1158 ProgramStateRef FalseState = bindInt(0, State, C, CE);
1159 C.addTransition(FalseState->set<StreamMap>(
1160 StreamSym,
1161 StreamState::getOpened(
1162 Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof())));
1163 }
1164}
1165
1166void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
1167 CheckerContext &C) const {
1168 ProgramStateRef State = C.getState();
1169 SVal StreamVal = getStreamArg(Desc, Call);
1170 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1171 State);
1172 if (!State)
1173 return;
1174 State = ensureStreamOpened(StreamVal, C, State);
1175 if (!State)
1176 return;
1177
1178 C.addTransition(State);
1179}
1180
1181void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
1182 const CallEvent &Call, CheckerContext &C,
1183 const StreamErrorState &ErrorKind) const {
1184 ProgramStateRef State = C.getState();
1185 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1186 assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
1187 const StreamState *SS = State->get<StreamMap>(StreamSym);
1188 assert(SS && "Stream should be tracked by the checker.");
1189 State = State->set<StreamMap>(
1190 StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind));
1191 C.addTransition(State);
1192}
1193
1195StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
1197 ProgramStateRef State) const {
1198 auto Stream = StreamVal.getAs<DefinedSVal>();
1199 if (!Stream)
1200 return State;
1201
1202 ConstraintManager &CM = C.getConstraintManager();
1203
1204 ProgramStateRef StateNotNull, StateNull;
1205 std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream);
1206
1207 if (!StateNotNull && StateNull) {
1208 if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
1209 auto R = std::make_unique<PathSensitiveBugReport>(
1210 BT_FileNull, "Stream pointer might be NULL.", N);
1211 if (StreamE)
1212 bugreporter::trackExpressionValue(N, StreamE, *R);
1213 C.emitReport(std::move(R));
1214 }
1215 return nullptr;
1216 }
1217
1218 return StateNotNull;
1219}
1220
1221ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
1223 ProgramStateRef State) const {
1224 SymbolRef Sym = StreamVal.getAsSymbol();
1225 if (!Sym)
1226 return State;
1227
1228 const StreamState *SS = State->get<StreamMap>(Sym);
1229 if (!SS)
1230 return State;
1231
1232 if (SS->isClosed()) {
1233 // Using a stream pointer after 'fclose' causes undefined behavior
1234 // according to cppreference.com .
1235 ExplodedNode *N = C.generateErrorNode();
1236 if (N) {
1237 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1238 BT_UseAfterClose,
1239 "Stream might be already closed. Causes undefined behaviour.", N));
1240 return nullptr;
1241 }
1242
1243 return State;
1244 }
1245
1246 if (SS->isOpenFailed()) {
1247 // Using a stream that has failed to open is likely to cause problems.
1248 // This should usually not occur because stream pointer is NULL.
1249 // But freopen can cause a state when stream pointer remains non-null but
1250 // failed to open.
1251 ExplodedNode *N = C.generateErrorNode();
1252 if (N) {
1253 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1254 BT_UseAfterOpenFailed,
1255 "Stream might be invalid after "
1256 "(re-)opening it has failed. "
1257 "Can cause undefined behaviour.",
1258 N));
1259 return nullptr;
1260 }
1261 }
1262
1263 return State;
1264}
1265
1266ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate(
1267 SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {
1268 static const char *BugMessage =
1269 "File position of the stream might be 'indeterminate' "
1270 "after a failed operation. "
1271 "Can cause undefined behavior.";
1272
1273 SymbolRef Sym = StreamVal.getAsSymbol();
1274 if (!Sym)
1275 return State;
1276
1277 const StreamState *SS = State->get<StreamMap>(Sym);
1278 if (!SS)
1279 return State;
1280
1281 assert(SS->isOpened() && "First ensure that stream is opened.");
1282
1283 if (SS->FilePositionIndeterminate) {
1284 if (SS->ErrorState & ErrorFEof) {
1285 // The error is unknown but may be FEOF.
1286 // Continue analysis with the FEOF error state.
1287 // Report warning because the other possible error states.
1288 ExplodedNode *N = C.generateNonFatalErrorNode(State);
1289 if (!N)
1290 return nullptr;
1291
1292 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1293 BT_IndeterminatePosition, BugMessage, N));
1294 return State->set<StreamMap>(
1295 Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false));
1296 }
1297
1298 // Known or unknown error state without FEOF possible.
1299 // Stop analysis, report error.
1300 ExplodedNode *N = C.generateErrorNode(State);
1301 if (N)
1302 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1303 BT_IndeterminatePosition, BugMessage, N));
1304
1305 return nullptr;
1306 }
1307
1308 return State;
1309}
1310
1312StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
1313 ProgramStateRef State) const {
1314 std::optional<nonloc::ConcreteInt> CI =
1315 WhenceVal.getAs<nonloc::ConcreteInt>();
1316 if (!CI)
1317 return State;
1318
1319 int64_t X = CI->getValue().getSExtValue();
1320 if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal)
1321 return State;
1322
1323 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1324 C.emitReport(std::make_unique<PathSensitiveBugReport>(
1325 BT_IllegalWhence,
1326 "The whence argument to fseek() should be "
1327 "SEEK_SET, SEEK_END, or SEEK_CUR.",
1328 N));
1329 return nullptr;
1330 }
1331
1332 return State;
1333}
1334
1335void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
1336 ProgramStateRef State) const {
1337 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1338 auto R = std::make_unique<PathSensitiveBugReport>(
1339 BT_StreamEof,
1340 "Read function called when stream is in EOF state. "
1341 "Function has no effect.",
1342 N);
1343 R->markInteresting(StreamSym);
1344 C.emitReport(std::move(R));
1345 return;
1346 }
1347 C.addTransition(State);
1348}
1349
1351StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
1352 CheckerContext &C, ExplodedNode *Pred) const {
1353 ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred);
1354 if (!Err)
1355 return Pred;
1356
1357 for (SymbolRef LeakSym : LeakedSyms) {
1358 // Resource leaks can result in multiple warning that describe the same kind
1359 // of programming error:
1360 // void f() {
1361 // FILE *F = fopen("a.txt");
1362 // if (rand()) // state split
1363 // return; // warning
1364 // } // warning
1365 // While this isn't necessarily true (leaking the same stream could result
1366 // from a different kinds of errors), the reduction in redundant reports
1367 // makes this a worthwhile heuristic.
1368 // FIXME: Add a checker option to turn this uniqueing feature off.
1369 const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C);
1370 assert(StreamOpenNode && "Could not find place of stream opening.");
1371
1372 PathDiagnosticLocation LocUsedForUniqueing;
1373 if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics())
1374 LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
1375 StreamStmt, C.getSourceManager(),
1376 StreamOpenNode->getLocationContext());
1377
1378 std::unique_ptr<PathSensitiveBugReport> R =
1379 std::make_unique<PathSensitiveBugReport>(
1380 BT_ResourceLeak,
1381 "Opened stream never closed. Potential resource leak.", Err,
1382 LocUsedForUniqueing,
1383 StreamOpenNode->getLocationContext()->getDecl());
1384 R->markInteresting(LeakSym);
1385 C.emitReport(std::move(R));
1386 }
1387
1388 return Err;
1389}
1390
1391void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1392 CheckerContext &C) const {
1393 ProgramStateRef State = C.getState();
1394
1396
1397 const StreamMapTy &Map = State->get<StreamMap>();
1398 for (const auto &I : Map) {
1399 SymbolRef Sym = I.first;
1400 const StreamState &SS = I.second;
1401 if (!SymReaper.isDead(Sym))
1402 continue;
1403 if (SS.isOpened())
1404 LeakedSyms.push_back(Sym);
1405 State = State->remove<StreamMap>(Sym);
1406 }
1407
1408 ExplodedNode *N = C.getPredecessor();
1409 if (!LeakedSyms.empty())
1410 N = reportLeaks(LeakedSyms, C, N);
1411
1412 C.addTransition(State, N);
1413}
1414
1415ProgramStateRef StreamChecker::checkPointerEscape(
1416 ProgramStateRef State, const InvalidatedSymbols &Escaped,
1417 const CallEvent *Call, PointerEscapeKind Kind) const {
1418 // Check for file-handling system call that is not handled by the checker.
1419 // FIXME: The checker should be updated to handle all system calls that take
1420 // 'FILE*' argument. These are now ignored.
1421 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader())
1422 return State;
1423
1424 for (SymbolRef Sym : Escaped) {
1425 // The symbol escaped.
1426 // From now the stream can be manipulated in unknown way to the checker,
1427 // it is not possible to handle it any more.
1428 // Optimistically, assume that the corresponding file handle will be closed
1429 // somewhere else.
1430 // Remove symbol from state so the following stream calls on this symbol are
1431 // not handled by the checker.
1432 State = State->remove<StreamMap>(Sym);
1433 }
1434 return State;
1435}
1436
1437//===----------------------------------------------------------------------===//
1438// Checker registration.
1439//===----------------------------------------------------------------------===//
1440
1441void ento::registerStreamChecker(CheckerManager &Mgr) {
1442 Mgr.registerChecker<StreamChecker>();
1443}
1444
1445bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {
1446 return true;
1447}
1448
1449void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
1450 auto *Checker = Mgr.getChecker<StreamChecker>();
1451 Checker->TestMode = true;
1452}
1453
1454bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
1455 return true;
1456}
StringRef P
#define X(type, name)
Definition: Value.h:142
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
void assertStreamStateOpened(const StreamState *SS)
__device__ int
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:182
CanQualType IntTy
Definition: ASTContext.h:1092
CanQualType UnsignedCharTy
Definition: ASTContext.h:1093
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2847
This represents one expression.
Definition: Expr.h:110
QualType getType() const
Definition: Expr.h:142
It wraps the AnalysisDeclContext to represent both the call stack with the help of StackFrameContext ...
const Decl * getDecl() const
A (possibly-)qualified type.
Definition: Type.h:736
Stmt - This represents one statement.
Definition: Stmt.h:84
bool isPointerType() const
Definition: Type.h:7033
bool isIntegralOrEnumerationType() const
Determine whether this type is an integral or enumeration type.
Definition: Type.h:7455
const llvm::APSInt & getMaxValue(const llvm::APSInt &v)
const BugType & getBugType() const
Definition: BugReporter.h:148
An immutable map from CallDescriptions to arbitrary data.
const T * lookup(const CallEvent &Call) const
Represents an abstract call to a function or method along a particular path.
Definition: CallEvent.h:152
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
ProgramStatePair assumeDual(ProgramStateRef State, DefinedSVal Cond)
Returns a pair of states (StTrue, StFalse) where the given condition is assumed to be true or false,...
const ProgramStateRef & getState() const
const Stmt * getStmtForDiagnostics() const
If the node's program point corresponds to a statement, retrieve that statement.
const LocationContext * getLocationContext() const
ExplodedNode * getFirstPred()
The tag upon which the TagVisitor reacts.
Definition: BugReporter.h:763
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
void markNotInteresting(SymbolRef sym)
bool isInteresting(SymbolRef sym) const
DefinedOrUnknownSVal makeZeroVal(QualType type)
Construct an SVal representing '0' for the specified type.
Definition: SValBuilder.cpp:62
BasicValueFactory & getBasicValueFactory()
Definition: SValBuilder.h:157
nonloc::ConcreteInt makeIntVal(const IntegerLiteral *integer)
Definition: SValBuilder.h:286
virtual SVal evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op, NonLoc lhs, NonLoc rhs, QualType resultTy)=0
Create a new value which represents a binary expression with two non- location operands.
QualType getConditionType() const
Definition: SValBuilder.h:149
SVal evalBinOp(ProgramStateRef state, BinaryOperator::Opcode op, SVal lhs, SVal rhs, QualType type)
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
Definition: SVals.h:55
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
Definition: SVals.cpp:104
std::optional< T > getAs() const
Convert to the specified SVal type, returning std::nullopt if this SVal is not of the desired type.
Definition: SVals.h:86
T castAs() const
Convert to the specified SVal type, asserting that this SVal is of the desired type.
Definition: SVals.h:82
Symbolic value.
Definition: SymExpr.h:30
A class responsible for cleaning up unused symbols.
bool isDead(SymbolRef sym)
Returns whether or not a symbol has been confirmed dead.
Value representing integer constant.
Definition: SVals.h:305
__inline void unsigned int _2
Definition: larchintrin.h:181
bool trackExpressionValue(const ExplodedNode *N, const Expr *E, PathSensitiveBugReport &R, TrackingOptions Opts={})
Attempts to add visitors to track expression value back to its point of origin.
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
@ PSK_DirectEscapeOnCall
The pointer has been passed to a function call directly.
std::optional< int > tryExpandAsInteger(StringRef Macro, const Preprocessor &PP)
Try to parse the value of a defined preprocessor macro.
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
Definition: CallGraph.h:207
DiagnosticLevelMask operator&(DiagnosticLevelMask LHS, DiagnosticLevelMask RHS)
DiagnosticLevelMask operator~(DiagnosticLevelMask M)
DiagnosticLevelMask operator|(DiagnosticLevelMask LHS, DiagnosticLevelMask RHS)
bool operator!=(CanQual< T > x, CanQual< U > y)
long int64_t
#define bool
Definition: stdbool.h:20