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