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