blob: a7b6f6c1fb55c9270e92bd73a1a5edc0c4e6b6f5 [file] [log] [blame]
//===-- StreamChecker.cpp -----------------------------------------*- C++ -*--//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines checkers that model and check stream handling functions.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
#include "llvm/ADT/Sequence.h"
#include <functional>
#include <optional>
using namespace clang;
using namespace ento;
using namespace std::placeholders;
//===----------------------------------------------------------------------===//
// Definition of state data structures.
//===----------------------------------------------------------------------===//
namespace {
struct FnDescription;
/// State of the stream error flags.
/// Sometimes it is not known to the checker what error flags are set.
/// This is indicated by setting more than one flag to true.
/// This is an optimization to avoid state splits.
/// A stream can either be in FEOF or FERROR but not both at the same time.
/// Multiple flags are set to handle the corresponding states together.
struct StreamErrorState {
/// The stream can be in state where none of the error flags set.
bool NoError = true;
/// The stream can be in state where the EOF indicator is set.
bool FEof = false;
/// The stream can be in state where the error indicator is set.
bool FError = false;
bool isNoError() const { return NoError && !FEof && !FError; }
bool isFEof() const { return !NoError && FEof && !FError; }
bool isFError() const { return !NoError && !FEof && FError; }
bool operator==(const StreamErrorState &ES) const {
return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError;
}
bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); }
StreamErrorState operator|(const StreamErrorState &E) const {
return {NoError || E.NoError, FEof || E.FEof, FError || E.FError};
}
StreamErrorState operator&(const StreamErrorState &E) const {
return {NoError && E.NoError, FEof && E.FEof, FError && E.FError};
}
StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; }
/// Returns if the StreamErrorState is a valid object.
operator bool() const { return NoError || FEof || FError; }
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddBoolean(NoError);
ID.AddBoolean(FEof);
ID.AddBoolean(FError);
}
};
const StreamErrorState ErrorNone{true, false, false};
const StreamErrorState ErrorFEof{false, true, false};
const StreamErrorState ErrorFError{false, false, true};
/// Full state information about a stream pointer.
struct StreamState {
/// The last file operation called in the stream.
/// Can be nullptr.
const FnDescription *LastOperation;
/// State of a stream symbol.
enum KindTy {
Opened, /// Stream is opened.
Closed, /// Closed stream (an invalid stream pointer after it was closed).
OpenFailed /// The last open operation has failed.
} State;
/// State of the error flags.
/// Ignored in non-opened stream state but must be NoError.
StreamErrorState const ErrorState;
/// Indicate if the file has an "indeterminate file position indicator".
/// This can be set at a failing read or write or seek operation.
/// If it is set no more read or write is allowed.
/// This value is not dependent on the stream error flags:
/// The error flag may be cleared with `clearerr` but the file position
/// remains still indeterminate.
/// This value applies to all error states in ErrorState except FEOF.
/// An EOF+indeterminate state is the same as EOF state.
bool const FilePositionIndeterminate = false;
StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES,
bool IsFilePositionIndeterminate)
: LastOperation(L), State(S), ErrorState(ES),
FilePositionIndeterminate(IsFilePositionIndeterminate) {
assert((!ES.isFEof() || !IsFilePositionIndeterminate) &&
"FilePositionIndeterminate should be false in FEof case.");
assert((State == Opened || ErrorState.isNoError()) &&
"ErrorState should be None in non-opened stream state.");
}
bool isOpened() const { return State == Opened; }
bool isClosed() const { return State == Closed; }
bool isOpenFailed() const { return State == OpenFailed; }
bool operator==(const StreamState &X) const {
// In not opened state error state should always NoError, so comparison
// here is no problem.
return LastOperation == X.LastOperation && State == X.State &&
ErrorState == X.ErrorState &&
FilePositionIndeterminate == X.FilePositionIndeterminate;
}
static StreamState getOpened(const FnDescription *L,
const StreamErrorState &ES = ErrorNone,
bool IsFilePositionIndeterminate = false) {
return StreamState{L, Opened, ES, IsFilePositionIndeterminate};
}
static StreamState getClosed(const FnDescription *L) {
return StreamState{L, Closed, {}, false};
}
static StreamState getOpenFailed(const FnDescription *L) {
return StreamState{L, OpenFailed, {}, false};
}
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddPointer(LastOperation);
ID.AddInteger(State);
ErrorState.Profile(ID);
ID.AddBoolean(FilePositionIndeterminate);
}
};
} // namespace
// This map holds the state of a stream.
// The stream is identified with a SymbolRef that is created when a stream
// opening function is modeled by the checker.
REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
//===----------------------------------------------------------------------===//
// StreamChecker class and utility functions.
//===----------------------------------------------------------------------===//
namespace {
class StreamChecker;
using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
const CallEvent &, CheckerContext &)>;
using ArgNoTy = unsigned int;
static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();
const char *FeofNote = "Assuming stream reaches end-of-file here";
const char *FerrorNote = "Assuming this stream operation fails";
struct FnDescription {
FnCheck PreFn;
FnCheck EvalFn;
ArgNoTy StreamArgNo;
};
/// Get the value of the stream argument out of the passed call event.
/// The call should contain a function that is described by Desc.
SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) {
assert(Desc && Desc->StreamArgNo != ArgNone &&
"Try to get a non-existing stream argument.");
return Call.getArgSVal(Desc->StreamArgNo);
}
/// Create a conjured symbol return value for a call expression.
DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) {
assert(CE && "Expecting a call expression.");
const LocationContext *LCtx = C.getLocationContext();
return C.getSValBuilder()
.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
.castAs<DefinedSVal>();
}
ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C,
const CallExpr *CE) {
DefinedSVal RetVal = makeRetVal(C, CE);
State = State->BindExpr(CE, C.getLocationContext(), RetVal);
State = State->assume(RetVal, true);
assert(State && "Assumption on new value should not fail.");
return State;
}
ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State,
CheckerContext &C, const CallExpr *CE) {
State = State->BindExpr(CE, C.getLocationContext(),
C.getSValBuilder().makeIntVal(Value, CE->getType()));
return State;
}
inline void assertStreamStateOpened(const StreamState *SS) {
assert(SS->isOpened() && "Stream is expected to be opened");
}
class StreamChecker : public Checker<check::PreCall, eval::Call,
check::DeadSymbols, check::PointerEscape> {
BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"};
BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"};
BugType BT_UseAfterOpenFailed{this, "Invalid stream",
"Stream handling error"};
BugType BT_IndeterminatePosition{this, "Invalid stream state",
"Stream handling error"};
BugType BT_IllegalWhence{this, "Illegal whence argument",
"Stream handling error"};
BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};
BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
/*SuppressOnSink =*/true};
public:
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
ProgramStateRef checkPointerEscape(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind) const;
const BugType *getBT_StreamEof() const { return &BT_StreamEof; }
const BugType *getBT_IndeterminatePosition() const {
return &BT_IndeterminatePosition;
}
const NoteTag *constructSetEofNoteTag(CheckerContext &C,
SymbolRef StreamSym) const {
return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
if (!BR.isInteresting(StreamSym) ||
&BR.getBugType() != this->getBT_StreamEof())
return "";
BR.markNotInteresting(StreamSym);
return FeofNote;
});
}
const NoteTag *constructSetErrorNoteTag(CheckerContext &C,
SymbolRef StreamSym) const {
return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
if (!BR.isInteresting(StreamSym) ||
&BR.getBugType() != this->getBT_IndeterminatePosition())
return "";
BR.markNotInteresting(StreamSym);
return FerrorNote;
});
}
const NoteTag *constructSetEofOrErrorNoteTag(CheckerContext &C,
SymbolRef StreamSym) const {
return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
if (!BR.isInteresting(StreamSym))
return "";
if (&BR.getBugType() == this->getBT_StreamEof()) {
BR.markNotInteresting(StreamSym);
return FeofNote;
}
if (&BR.getBugType() == this->getBT_IndeterminatePosition()) {
BR.markNotInteresting(StreamSym);
return FerrorNote;
}
return "";
});
}
/// If true, evaluate special testing stream functions.
bool TestMode = false;
/// If true, generate failure branches for cases that are often not checked.
bool PedanticMode = false;
private:
CallDescriptionMap<FnDescription> FnDescriptions = {
{{CDM::CLibrary, {"fopen"}, 2},
{nullptr, &StreamChecker::evalFopen, ArgNone}},
{{CDM::CLibrary, {"fdopen"}, 2},
{nullptr, &StreamChecker::evalFopen, ArgNone}},
{{CDM::CLibrary, {"freopen"}, 3},
{&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
{{CDM::CLibrary, {"tmpfile"}, 0},
{nullptr, &StreamChecker::evalFopen, ArgNone}},
{{CDM::CLibrary, {"fclose"}, 1},
{&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
{{CDM::CLibrary, {"fread"}, 4},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},
{{CDM::CLibrary, {"fwrite"}, 4},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},
{{CDM::CLibrary, {"fgetc"}, 1},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
{{CDM::CLibrary, {"fgets"}, 3},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},
{{CDM::CLibrary, {"getc"}, 1},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
{{CDM::CLibrary, {"fputc"}, 2},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
{{CDM::CLibrary, {"fputs"}, 2},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
{{CDM::CLibrary, {"putc"}, 2},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
{{CDM::CLibrary, {"fprintf"}},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
{{CDM::CLibrary, {"vfprintf"}, 3},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
{{CDM::CLibrary, {"fscanf"}},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
{{CDM::CLibrary, {"vfscanf"}, 3},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
{{CDM::CLibrary, {"ungetc"}, 2},
{&StreamChecker::preWrite,
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
{{CDM::CLibrary, {"getdelim"}, 4},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
{{CDM::CLibrary, {"getline"}, 3},
{&StreamChecker::preRead,
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
{{CDM::CLibrary, {"fseek"}, 3},
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
{{CDM::CLibrary, {"fseeko"}, 3},
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
{{CDM::CLibrary, {"ftell"}, 1},
{&StreamChecker::preWrite, &StreamChecker::evalFtell, 0}},
{{CDM::CLibrary, {"ftello"}, 1},
{&StreamChecker::preWrite, &StreamChecker::evalFtell, 0}},
{{CDM::CLibrary, {"fflush"}, 1},
{&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}},
{{CDM::CLibrary, {"rewind"}, 1},
{&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
{{CDM::CLibrary, {"fgetpos"}, 2},
{&StreamChecker::preWrite, &StreamChecker::evalFgetpos, 0}},
{{CDM::CLibrary, {"fsetpos"}, 2},
{&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},
{{CDM::CLibrary, {"clearerr"}, 1},
{&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
{{CDM::CLibrary, {"feof"}, 1},
{&StreamChecker::preDefault,
std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof),
0}},
{{CDM::CLibrary, {"ferror"}, 1},
{&StreamChecker::preDefault,
std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError),
0}},
{{CDM::CLibrary, {"fileno"}, 1},
{&StreamChecker::preDefault, &StreamChecker::evalFileno, 0}},
};
CallDescriptionMap<FnDescription> FnTestDescriptions = {
{{CDM::SimpleFunc, {"StreamTesterChecker_make_feof_stream"}, 1},
{nullptr,
std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof,
false),
0}},
{{CDM::SimpleFunc, {"StreamTesterChecker_make_ferror_stream"}, 1},
{nullptr,
std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
ErrorFError, false),
0}},
{{CDM::SimpleFunc,
{"StreamTesterChecker_make_ferror_indeterminate_stream"},
1},
{nullptr,
std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
ErrorFError, true),
0}},
};
/// Expanded value of EOF, empty before initialization.
mutable std::optional<int> EofVal;
/// Expanded value of SEEK_SET, 0 if not found.
mutable int SeekSetVal = 0;
/// Expanded value of SEEK_CUR, 1 if not found.
mutable int SeekCurVal = 1;
/// Expanded value of SEEK_END, 2 if not found.
mutable int SeekEndVal = 2;
/// The built-in va_list type is platform-specific
mutable QualType VaListType;
void evalFopen(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void preFreopen(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFclose(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void preRead(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void preWrite(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C, bool IsFread) const;
void evalFgetx(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C, bool SingleChar) const;
void evalFputx(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C, bool IsSingleChar) const;
void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFscanf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void preFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFtell(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalRewind(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void preDefault(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C,
const StreamErrorState &ErrorKind) const;
void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C, const StreamErrorState &ErrorKind,
bool Indeterminate) const;
void preFflush(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFflush(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFileno(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
/// Check that the stream (in StreamVal) is not NULL.
/// If it can only be NULL a fatal error is emitted and nullptr returned.
/// Otherwise the return value is a new state where the stream is constrained
/// to be non-null.
ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
CheckerContext &C,
ProgramStateRef State) const;
/// Check that the stream is the opened state.
/// If the stream is known to be not opened an error is generated
/// and nullptr returned, otherwise the original state is returned.
ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
ProgramStateRef State) const;
/// Check that the stream has not an invalid ("indeterminate") file position,
/// generate warning for it.
/// (EOF is not an invalid position.)
/// The returned state can be nullptr if a fatal error was generated.
/// It can return non-null state if the stream has not an invalid position or
/// there is execution path with non-invalid position.
ProgramStateRef
ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C,
ProgramStateRef State) const;
/// Check the legality of the 'whence' argument of 'fseek'.
/// Generate error and return nullptr if it is found to be illegal.
/// Otherwise returns the state.
/// (State is not changed here because the "whence" value is already known.)
ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
ProgramStateRef State) const;
/// Generate warning about stream in EOF state.
/// There will be always a state transition into the passed State,
/// by the new non-fatal error node or (if failed) a normal transition,
/// to ensure uniform handling.
void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
ProgramStateRef State) const;
/// Emit resource leak warnings for the given symbols.
/// Createn a non-fatal error node for these, and returns it (if any warnings
/// were generated). Return value is non-null.
ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
CheckerContext &C, ExplodedNode *Pred) const;
/// Find the description data of the function called by a call event.
/// Returns nullptr if no function is recognized.
const FnDescription *lookupFn(const CallEvent &Call) const {
// Recognize "global C functions" with only integral or pointer arguments
// (and matching name) as stream functions.
for (auto *P : Call.parameters()) {
QualType T = P->getType();
if (!T->isIntegralOrEnumerationType() && !T->isPointerType() &&
T.getCanonicalType() != VaListType)
return nullptr;
}
return FnDescriptions.lookup(Call);
}
/// Generate a message for BugReporterVisitor if the stored symbol is
/// marked as interesting by the actual bug report.
const NoteTag *constructLeakNoteTag(CheckerContext &C, SymbolRef StreamSym,
const std::string &Message) const {
return C.getNoteTag([this, StreamSym,
Message](PathSensitiveBugReport &BR) -> std::string {
if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak)
return Message;
return "";
});
}
void initMacroValues(CheckerContext &C) const {
if (EofVal)
return;
if (const std::optional<int> OptInt =
tryExpandAsInteger("EOF", C.getPreprocessor()))
EofVal = *OptInt;
else
EofVal = -1;
if (const std::optional<int> OptInt =
tryExpandAsInteger("SEEK_SET", C.getPreprocessor()))
SeekSetVal = *OptInt;
if (const std::optional<int> OptInt =
tryExpandAsInteger("SEEK_END", C.getPreprocessor()))
SeekEndVal = *OptInt;
if (const std::optional<int> OptInt =
tryExpandAsInteger("SEEK_CUR", C.getPreprocessor()))
SeekCurVal = *OptInt;
}
void initVaListType(CheckerContext &C) const {
VaListType = C.getASTContext().getBuiltinVaListType().getCanonicalType();
}
/// Searches for the ExplodedNode where the file descriptor was acquired for
/// StreamSym.
static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
SymbolRef StreamSym,
CheckerContext &C);
};
struct StreamOperationEvaluator {
SValBuilder &SVB;
const ASTContext &ACtx;
SymbolRef StreamSym = nullptr;
const StreamState *SS = nullptr;
const CallExpr *CE = nullptr;
StreamErrorState NewES;
StreamOperationEvaluator(CheckerContext &C)
: SVB(C.getSValBuilder()), ACtx(C.getASTContext()) {
;
}
bool Init(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C,
ProgramStateRef State) {
StreamSym = getStreamArg(Desc, Call).getAsSymbol();
if (!StreamSym)
return false;
SS = State->get<StreamMap>(StreamSym);
if (!SS)
return false;
NewES = SS->ErrorState;
CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return false;
assertStreamStateOpened(SS);
return true;
}
bool isStreamEof() const { return SS->ErrorState == ErrorFEof; }
NonLoc getZeroVal(const CallEvent &Call) {
return *SVB.makeZeroVal(Call.getResultType()).getAs<NonLoc>();
}
ProgramStateRef setStreamState(ProgramStateRef State,
const StreamState &NewSS) {
NewES = NewSS.ErrorState;
return State->set<StreamMap>(StreamSym, NewSS);
}
ProgramStateRef makeAndBindRetVal(ProgramStateRef State, CheckerContext &C) {
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
return State->BindExpr(CE, C.getLocationContext(), RetVal);
}
ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C,
uint64_t Val) {
return State->BindExpr(CE, C.getLocationContext(),
SVB.makeIntVal(Val, CE->getCallReturnType(ACtx)));
}
ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C,
SVal Val) {
return State->BindExpr(CE, C.getLocationContext(), Val);
}
ProgramStateRef bindNullReturnValue(ProgramStateRef State,
CheckerContext &C) {
return State->BindExpr(CE, C.getLocationContext(),
C.getSValBuilder().makeNullWithType(CE->getType()));
}
ProgramStateRef assumeBinOpNN(ProgramStateRef State,
BinaryOperator::Opcode Op, NonLoc LHS,
NonLoc RHS) {
auto Cond = SVB.evalBinOpNN(State, Op, LHS, RHS, SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
if (!Cond)
return nullptr;
return State->assume(*Cond, true);
}
ConstraintManager::ProgramStatePair
makeRetValAndAssumeDual(ProgramStateRef State, CheckerContext &C) {
DefinedSVal RetVal = makeRetVal(C, CE);
State = State->BindExpr(CE, C.getLocationContext(), RetVal);
return C.getConstraintManager().assumeDual(State, RetVal);
}
const NoteTag *getFailureNoteTag(const StreamChecker *Ch, CheckerContext &C) {
bool SetFeof = NewES.FEof && !SS->ErrorState.FEof;
bool SetFerror = NewES.FError && !SS->ErrorState.FError;
if (SetFeof && !SetFerror)
return Ch->constructSetEofNoteTag(C, StreamSym);
if (!SetFeof && SetFerror)
return Ch->constructSetErrorNoteTag(C, StreamSym);
if (SetFeof && SetFerror)
return Ch->constructSetEofOrErrorNoteTag(C, StreamSym);
return nullptr;
}
};
} // end anonymous namespace
const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,
SymbolRef StreamSym,
CheckerContext &C) {
ProgramStateRef State = N->getState();
// When bug type is resource leak, exploded node N may not have state info
// for leaked file descriptor, but predecessor should have it.
if (!State->get<StreamMap>(StreamSym))
N = N->getFirstPred();
const ExplodedNode *Pred = N;
while (N) {
State = N->getState();
if (!State->get<StreamMap>(StreamSym))
return Pred;
Pred = N;
N = N->getFirstPred();
}
return nullptr;
}
static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
const CallEvent &Call,
ArrayRef<unsigned int> EscapingArgs) {
const auto *CE = Call.getOriginExpr();
SmallVector<SVal> EscapingVals;
EscapingVals.reserve(EscapingArgs.size());
for (auto EscArgIdx : EscapingArgs)
EscapingVals.push_back(Call.getArgSVal(EscArgIdx));
State = State->invalidateRegions(EscapingVals, CE, C.blockCount(),
C.getLocationContext(),
/*CausesPointerEscape=*/false);
return State;
}
//===----------------------------------------------------------------------===//
// Methods of StreamChecker.
//===----------------------------------------------------------------------===//
void StreamChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
initMacroValues(C);
initVaListType(C);
const FnDescription *Desc = lookupFn(Call);
if (!Desc || !Desc->PreFn)
return;
Desc->PreFn(this, Desc, Call, C);
}
bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
const FnDescription *Desc = lookupFn(Call);
if (!Desc && TestMode)
Desc = FnTestDescriptions.lookup(Call);
if (!Desc || !Desc->EvalFn)
return false;
Desc->EvalFn(this, Desc, Call, C);
return C.isDifferent();
}
void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return;
DefinedSVal RetVal = makeRetVal(C, CE);
SymbolRef RetSym = RetVal.getAsSymbol();
assert(RetSym && "RetVal must be a symbol here.");
State = State->BindExpr(CE, C.getLocationContext(), RetVal);
// Bifurcate the state into two: one with a valid FILE* pointer, the other
// with a NULL.
ProgramStateRef StateNotNull, StateNull;
std::tie(StateNotNull, StateNull) =
C.getConstraintManager().assumeDual(State, RetVal);
StateNotNull =
StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
StateNull =
StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
C.addTransition(StateNotNull,
constructLeakNoteTag(C, RetSym, "Stream opened here"));
C.addTransition(StateNull);
}
void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
// Do not allow NULL as passed stream pointer but allow a closed stream.
ProgramStateRef State = C.getState();
State = ensureStreamNonNull(getStreamArg(Desc, Call),
Call.getArgExpr(Desc->StreamArgNo), C, State);
if (!State)
return;
C.addTransition(State);
}
void StreamChecker::evalFreopen(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return;
std::optional<DefinedSVal> StreamVal =
getStreamArg(Desc, Call).getAs<DefinedSVal>();
if (!StreamVal)
return;
SymbolRef StreamSym = StreamVal->getAsSymbol();
// Do not care about concrete values for stream ("(FILE *)0x12345"?).
// FIXME: Can be stdin, stdout, stderr such values?
if (!StreamSym)
return;
// Do not handle untracked stream. It is probably escaped.
if (!State->get<StreamMap>(StreamSym))
return;
// Generate state for non-failed case.
// Return value is the passed stream pointer.
// According to the documentations, the stream is closed first
// but any close error is ignored. The state changes to (or remains) opened.
ProgramStateRef StateRetNotNull =
State->BindExpr(CE, C.getLocationContext(), *StreamVal);
// Generate state for NULL return value.
// Stream switches to OpenFailed state.
ProgramStateRef StateRetNull =
State->BindExpr(CE, C.getLocationContext(),
C.getSValBuilder().makeNullWithType(CE->getType()));
StateRetNotNull =
StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
StateRetNull =
StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
C.addTransition(StateRetNotNull,
constructLeakNoteTag(C, StreamSym, "Stream reopened here"));
C.addTransition(StateRetNull);
}
void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
// Close the File Descriptor.
// Regardless if the close fails or not, stream becomes "closed"
// and can not be used any more.
State = E.setStreamState(State, StreamState::getClosed(Desc));
// Return 0 on success, EOF on failure.
C.addTransition(E.bindReturnValue(State, C, 0));
C.addTransition(E.bindReturnValue(State, C, *EofVal));
}
void StreamChecker::preRead(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal StreamVal = getStreamArg(Desc, Call);
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
State);
if (!State)
return;
State = ensureStreamOpened(StreamVal, C, State);
if (!State)
return;
State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
if (!State)
return;
SymbolRef Sym = StreamVal.getAsSymbol();
if (Sym && State->get<StreamMap>(Sym)) {
const StreamState *SS = State->get<StreamMap>(Sym);
if (SS->ErrorState & ErrorFEof)
reportFEofWarning(Sym, C, State);
} else {
C.addTransition(State);
}
}
void StreamChecker::preWrite(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal StreamVal = getStreamArg(Desc, Call);
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
State);
if (!State)
return;
State = ensureStreamOpened(StreamVal, C, State);
if (!State)
return;
State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
if (!State)
return;
C.addTransition(State);
}
void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
const CallEvent &Call, CheckerContext &C,
bool IsFread) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();
if (!SizeVal)
return;
std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>();
if (!NMembVal)
return;
// C'99 standard, §7.19.8.1.3, the return value of fread:
// The fread function returns the number of elements successfully read, which
// may be less than nmemb if a read error or end-of-file is encountered. If
// size or nmemb is zero, fread returns zero and the contents of the array and
// the state of the stream remain unchanged.
if (State->isNull(*SizeVal).isConstrainedTrue() ||
State->isNull(*NMembVal).isConstrainedTrue()) {
// This is the "size or nmemb is zero" case.
// Just return 0, do nothing more (not clear the error flags).
C.addTransition(E.bindReturnValue(State, C, 0));
return;
}
// At read, invalidate the buffer in any case of error or success,
// except if EOF was already present.
if (IsFread && !E.isStreamEof())
State = escapeArgs(State, C, Call, {0});
// Generate a transition for the success state.
// If we know the state to be FEOF at fread, do not add a success state.
if (!IsFread || !E.isStreamEof()) {
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), *NMembVal);
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
}
// Add transition for the failed state.
// At write, add failure case only if "pedantic mode" is on.
if (!IsFread && !PedanticMode)
return;
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
ProgramStateRef StateFailed =
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
StateFailed = E.assumeBinOpNN(StateFailed, BO_LT, RetVal, *NMembVal);
if (!StateFailed)
return;
StreamErrorState NewES;
if (IsFread)
NewES = E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
else
NewES = ErrorFError;
// If a (non-EOF) error occurs, the resulting value of the file position
// indicator for the stream is indeterminate.
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C, bool SingleChar) const {
// `fgetc` returns the read character on success, otherwise returns EOF.
// `fgets` returns the read buffer address on success, otherwise returns NULL.
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
if (!E.isStreamEof()) {
// If there was already EOF, assume that read buffer is not changed.
// Otherwise it may change at success or failure.
State = escapeArgs(State, C, Call, {0});
if (SingleChar) {
// Generate a transition for the success state of `fgetc`.
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
// The returned 'unsigned char' of `fgetc` is converted to 'int',
// so we need to check if it is in range [0, 255].
StateNotFailed = StateNotFailed->assumeInclusiveRange(
RetVal,
E.SVB.getBasicValueFactory().getValue(0, E.ACtx.UnsignedCharTy),
E.SVB.getBasicValueFactory().getMaxValue(E.ACtx.UnsignedCharTy),
true);
if (!StateNotFailed)
return;
C.addTransition(StateNotFailed);
} else {
// Generate a transition for the success state of `fgets`.
std::optional<DefinedSVal> GetBuf =
Call.getArgSVal(0).getAs<DefinedSVal>();
if (!GetBuf)
return;
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), *GetBuf);
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
}
}
// Add transition for the failed state.
ProgramStateRef StateFailed;
if (SingleChar)
StateFailed = E.bindReturnValue(State, C, *EofVal);
else
StateFailed = E.bindNullReturnValue(State, C);
// If a (non-EOF) error occurs, the resulting value of the file position
// indicator for the stream is indeterminate.
StreamErrorState NewES =
E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C, bool IsSingleChar) const {
// `fputc` returns the written character on success, otherwise returns EOF.
// `fputs` returns a nonnegative value on success, otherwise returns EOF.
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
if (IsSingleChar) {
// Generate a transition for the success state of `fputc`.
std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
if (!PutVal)
return;
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), *PutVal);
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
} else {
// Generate a transition for the success state of `fputs`.
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
StateNotFailed =
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
if (!StateNotFailed)
return;
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
}
if (!PedanticMode)
return;
// Add transition for the failed state. The resulting value of the file
// position indicator for the stream is indeterminate.
ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, ErrorFError, true));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalFprintf(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
if (Call.getNumArgs() < 2)
return;
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
State = State->BindExpr(E.CE, C.getLocationContext(), RetVal);
auto Cond =
E.SVB
.evalBinOp(State, BO_GE, RetVal, E.SVB.makeZeroVal(E.ACtx.IntTy),
E.SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
if (!Cond)
return;
ProgramStateRef StateNotFailed, StateFailed;
std::tie(StateNotFailed, StateFailed) = State->assume(*Cond);
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
if (!PedanticMode)
return;
// Add transition for the failed state. The resulting value of the file
// position indicator for the stream is indeterminate.
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, ErrorFError, true));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
if (Call.getNumArgs() < 2)
return;
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
// Add the success state.
// In this context "success" means there is not an EOF or other read error
// before any item is matched in 'fscanf'. But there may be match failure,
// therefore return value can be 0 or greater.
// It is not specified what happens if some items (not all) are matched and
// then EOF or read error happens. Now this case is handled like a "success"
// case, and no error flags are set on the stream. This is probably not
// accurate, and the POSIX documentation does not tell more.
if (!E.isStreamEof()) {
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
StateNotFailed =
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
if (!StateNotFailed)
return;
if (auto const *Callee = Call.getCalleeIdentifier();
!Callee || !Callee->getName().equals("vfscanf")) {
SmallVector<unsigned int> EscArgs;
for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))
EscArgs.push_back(EscArg);
StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);
}
if (StateNotFailed)
C.addTransition(StateNotFailed);
}
// Add transition for the failed state.
// Error occurs if nothing is matched yet and reading the input fails.
// Error can be EOF, or other error. At "other error" FERROR or 'errno' can
// be set but it is not further specified if all are required to be set.
// Documentation does not mention, but file position will be set to
// indeterminate similarly as at 'fread'.
ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
StreamErrorState NewES =
E.isStreamEof() ? ErrorFEof : ErrorNone | ErrorFEof | ErrorFError;
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
// Generate a transition for the success state.
std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
if (!PutVal)
return;
ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, *PutVal);
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
// Add transition for the failed state.
// Failure of 'ungetc' does not result in feof or ferror state.
// If the PutVal has value of EofVal the function should "fail", but this is
// the same transition as the success state.
// In this case only one state transition is added by the analyzer (the two
// new states may be similar).
ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
StateFailed = E.setStreamState(StateFailed, StreamState::getOpened(Desc));
C.addTransition(StateFailed);
}
void StreamChecker::evalGetdelim(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
// Upon successful completion, the getline() and getdelim() functions shall
// return the number of bytes written into the buffer.
// If the end-of-file indicator for the stream is set, the function shall
// return -1.
// If an error occurs, the function shall return -1 and set 'errno'.
if (!E.isStreamEof()) {
// Escape buffer and size (may change by the call).
// May happen even at error (partial read?).
State = escapeArgs(State, C, Call, {0, 1});
// Add transition for the successful state.
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, RetVal);
StateNotFailed =
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
// On success, a buffer is allocated.
auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State);
if (NewLinePtr && isa<DefinedOrUnknownSVal>(*NewLinePtr))
StateNotFailed = StateNotFailed->assume(
NewLinePtr->castAs<DefinedOrUnknownSVal>(), true);
// The buffer size `*n` must be enough to hold the whole line, and
// greater than the return value, since it has to account for '\0'.
SVal SizePtrSval = Call.getArgSVal(1);
auto NVal = getPointeeVal(SizePtrSval, State);
if (NVal && isa<NonLoc>(*NVal)) {
StateNotFailed = E.assumeBinOpNN(StateNotFailed, BO_GT,
NVal->castAs<NonLoc>(), RetVal);
StateNotFailed = E.bindReturnValue(StateNotFailed, C, RetVal);
}
if (!StateNotFailed)
return;
C.addTransition(StateNotFailed);
}
// Add transition for the failed state.
// If a (non-EOF) error occurs, the resulting value of the file position
// indicator for the stream is indeterminate.
ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
StreamErrorState NewES =
E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
// On failure, the content of the buffer is undefined.
if (auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State))
StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),
C.getLocationContext());
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal StreamVal = getStreamArg(Desc, Call);
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
State);
if (!State)
return;
State = ensureStreamOpened(StreamVal, C, State);
if (!State)
return;
State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
if (!State)
return;
C.addTransition(State);
}
void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
// Add success state.
ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, 0);
// No failure: Reset the state to opened with no error.
StateNotFailed =
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
C.addTransition(StateNotFailed);
if (!PedanticMode)
return;
// Add failure state.
// At error it is possible that fseek fails but sets none of the error flags.
// If fseek failed, assume that the file position becomes indeterminate in any
// case.
// It is allowed to set the position beyond the end of the file. EOF error
// should not occur.
ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalFgetpos(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
ProgramStateRef StateNotFailed, StateFailed;
std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C);
StateNotFailed = escapeArgs(StateNotFailed, C, Call, {1});
// This function does not affect the stream state.
// Still we add success and failure state with the appropriate return value.
// StdLibraryFunctionsChecker can change these states (set the 'errno' state).
C.addTransition(StateNotFailed);
C.addTransition(StateFailed);
}
void StreamChecker::evalFsetpos(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
ProgramStateRef StateNotFailed, StateFailed;
std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C);
StateNotFailed = E.setStreamState(
StateNotFailed, StreamState::getOpened(Desc, ErrorNone, false));
C.addTransition(StateNotFailed);
if (!PedanticMode)
return;
// At failure ferror could be set.
// The standards do not tell what happens with the file position at failure.
// But we can assume that it is dangerous to make a next I/O operation after
// the position was not set correctly (similar to 'fseek').
StateFailed = E.setStreamState(
StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
}
void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
StateNotFailed =
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
if (!StateNotFailed)
return;
ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
// This function does not affect the stream state.
// Still we add success and failure state with the appropriate return value.
// StdLibraryFunctionsChecker can change these states (set the 'errno' state).
C.addTransition(StateNotFailed);
C.addTransition(StateFailed);
}
void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
State =
E.setStreamState(State, StreamState::getOpened(Desc, ErrorNone, false));
C.addTransition(State);
}
void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal StreamVal = getStreamArg(Desc, Call);
std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
if (!Stream)
return;
ProgramStateRef StateNotNull, StateNull;
std::tie(StateNotNull, StateNull) =
C.getConstraintManager().assumeDual(State, *Stream);
if (StateNotNull && !StateNull)
ensureStreamOpened(StreamVal, C, StateNotNull);
}
void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal StreamVal = getStreamArg(Desc, Call);
std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
if (!Stream)
return;
// Skip if the stream can be both NULL and non-NULL.
ProgramStateRef StateNotNull, StateNull;
std::tie(StateNotNull, StateNull) =
C.getConstraintManager().assumeDual(State, *Stream);
if (StateNotNull && StateNull)
return;
if (StateNotNull && !StateNull)
State = StateNotNull;
else
State = StateNull;
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return;
// `fflush` returns EOF on failure, otherwise returns 0.
ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
ProgramStateRef StateNotFailed = bindInt(0, State, C, CE);
// Clear error states if `fflush` returns 0, but retain their EOF flags.
auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym,
const StreamState *SS) {
if (SS->ErrorState & ErrorFError) {
StreamErrorState NewES =
(SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone;
StreamState NewSS = StreamState::getOpened(Desc, NewES, false);
StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS);
}
};
if (StateNotNull && !StateNull) {
// Skip if the input stream's state is unknown, open-failed or closed.
if (SymbolRef StreamSym = StreamVal.getAsSymbol()) {
const StreamState *SS = State->get<StreamMap>(StreamSym);
if (SS) {
assert(SS->isOpened() && "Stream is expected to be opened");
ClearErrorInNotFailed(StreamSym, SS);
} else
return;
}
} else {
// Clear error states for all streams.
const StreamMapTy &Map = StateNotFailed->get<StreamMap>();
for (const auto &I : Map) {
SymbolRef Sym = I.first;
const StreamState &SS = I.second;
if (SS.isOpened())
ClearErrorInNotFailed(Sym, &SS);
}
}
C.addTransition(StateNotFailed);
C.addTransition(StateFailed);
}
void StreamChecker::evalClearerr(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
// FilePositionIndeterminate is not cleared.
State = E.setStreamState(
State,
StreamState::getOpened(Desc, ErrorNone, E.SS->FilePositionIndeterminate));
C.addTransition(State);
}
void StreamChecker::evalFeofFerror(const FnDescription *Desc,
const CallEvent &Call, CheckerContext &C,
const StreamErrorState &ErrorKind) const {
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
if (E.SS->ErrorState & ErrorKind) {
// Execution path with error of ErrorKind.
// Function returns true.
// From now on it is the only one error state.
ProgramStateRef TrueState = bindAndAssumeTrue(State, C, E.CE);
C.addTransition(E.setStreamState(
TrueState, StreamState::getOpened(Desc, ErrorKind,
E.SS->FilePositionIndeterminate &&
!ErrorKind.isFEof())));
}
if (StreamErrorState NewES = E.SS->ErrorState & (~ErrorKind)) {
// Execution path(s) with ErrorKind not set.
// Function returns false.
// New error state is everything before minus ErrorKind.
ProgramStateRef FalseState = E.bindReturnValue(State, C, 0);
C.addTransition(E.setStreamState(
FalseState,
StreamState::getOpened(
Desc, NewES, E.SS->FilePositionIndeterminate && !NewES.isFEof())));
}
}
void StreamChecker::evalFileno(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
// Fileno should fail only if the passed pointer is invalid.
// Some of the preconditions are checked already in preDefault.
// Here we can assume that the operation does not fail, because if we
// introduced a separate branch where fileno() returns -1, then it would cause
// many unexpected and unwanted warnings in situations where fileno() is
// called on valid streams.
// The stream error states are not modified by 'fileno', and 'errno' is also
// left unchanged (so this evalCall does not invalidate it, but we have a
// custom evalCall instead of the default that would invalidate it).
ProgramStateRef State = C.getState();
StreamOperationEvaluator E(C);
if (!E.Init(Desc, Call, C, State))
return;
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
State = State->BindExpr(E.CE, C.getLocationContext(), RetVal);
State = E.assumeBinOpNN(State, BO_GE, RetVal, E.getZeroVal(Call));
if (!State)
return;
C.addTransition(State);
}
void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal StreamVal = getStreamArg(Desc, Call);
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
State);
if (!State)
return;
State = ensureStreamOpened(StreamVal, C, State);
if (!State)
return;
C.addTransition(State);
}
void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
const CallEvent &Call, CheckerContext &C,
const StreamErrorState &ErrorKind,
bool Indeterminate) const {
ProgramStateRef State = C.getState();
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
const StreamState *SS = State->get<StreamMap>(StreamSym);
assert(SS && "Stream should be tracked by the checker.");
State = State->set<StreamMap>(
StreamSym,
StreamState::getOpened(SS->LastOperation, ErrorKind, Indeterminate));
C.addTransition(State);
}
ProgramStateRef
StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
CheckerContext &C,
ProgramStateRef State) const {
auto Stream = StreamVal.getAs<DefinedSVal>();
if (!Stream)
return State;
ConstraintManager &CM = C.getConstraintManager();
ProgramStateRef StateNotNull, StateNull;
std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream);
if (!StateNotNull && StateNull) {
if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
auto R = std::make_unique<PathSensitiveBugReport>(
BT_FileNull, "Stream pointer might be NULL.", N);
if (StreamE)
bugreporter::trackExpressionValue(N, StreamE, *R);
C.emitReport(std::move(R));
}
return nullptr;
}
return StateNotNull;
}
ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
CheckerContext &C,
ProgramStateRef State) const {
SymbolRef Sym = StreamVal.getAsSymbol();
if (!Sym)
return State;
const StreamState *SS = State->get<StreamMap>(Sym);
if (!SS)
return State;
if (SS->isClosed()) {
// Using a stream pointer after 'fclose' causes undefined behavior
// according to cppreference.com .
ExplodedNode *N = C.generateErrorNode();
if (N) {
C.emitReport(std::make_unique<PathSensitiveBugReport>(
BT_UseAfterClose,
"Stream might be already closed. Causes undefined behaviour.", N));
return nullptr;
}
return State;
}
if (SS->isOpenFailed()) {
// Using a stream that has failed to open is likely to cause problems.
// This should usually not occur because stream pointer is NULL.
// But freopen can cause a state when stream pointer remains non-null but
// failed to open.
ExplodedNode *N = C.generateErrorNode();
if (N) {
C.emitReport(std::make_unique<PathSensitiveBugReport>(
BT_UseAfterOpenFailed,
"Stream might be invalid after "
"(re-)opening it has failed. "
"Can cause undefined behaviour.",
N));
return nullptr;
}
}
return State;
}
ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate(
SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {
static const char *BugMessage =
"File position of the stream might be 'indeterminate' "
"after a failed operation. "
"Can cause undefined behavior.";
SymbolRef Sym = StreamVal.getAsSymbol();
if (!Sym)
return State;
const StreamState *SS = State->get<StreamMap>(Sym);
if (!SS)
return State;
assert(SS->isOpened() && "First ensure that stream is opened.");
if (SS->FilePositionIndeterminate) {
if (SS->ErrorState & ErrorFEof) {
// The error is unknown but may be FEOF.
// Continue analysis with the FEOF error state.
// Report warning because the other possible error states.
ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
return nullptr;
auto R = std::make_unique<PathSensitiveBugReport>(
BT_IndeterminatePosition, BugMessage, N);
R->markInteresting(Sym);
C.emitReport(std::move(R));
return State->set<StreamMap>(
Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false));
}
// Known or unknown error state without FEOF possible.
// Stop analysis, report error.
if (ExplodedNode *N = C.generateErrorNode(State)) {
auto R = std::make_unique<PathSensitiveBugReport>(
BT_IndeterminatePosition, BugMessage, N);
R->markInteresting(Sym);
C.emitReport(std::move(R));
}
return nullptr;
}
return State;
}
ProgramStateRef
StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
ProgramStateRef State) const {
std::optional<nonloc::ConcreteInt> CI =
WhenceVal.getAs<nonloc::ConcreteInt>();
if (!CI)
return State;
int64_t X = CI->getValue().getSExtValue();
if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal)
return State;
if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
C.emitReport(std::make_unique<PathSensitiveBugReport>(
BT_IllegalWhence,
"The whence argument to fseek() should be "
"SEEK_SET, SEEK_END, or SEEK_CUR.",
N));
return nullptr;
}
return State;
}
void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
ProgramStateRef State) const {
if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
auto R = std::make_unique<PathSensitiveBugReport>(
BT_StreamEof,
"Read function called when stream is in EOF state. "
"Function has no effect.",
N);
R->markInteresting(StreamSym);
C.emitReport(std::move(R));
return;
}
C.addTransition(State);
}
ExplodedNode *
StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
CheckerContext &C, ExplodedNode *Pred) const {
ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred);
if (!Err)
return Pred;
for (SymbolRef LeakSym : LeakedSyms) {
// Resource leaks can result in multiple warning that describe the same kind
// of programming error:
// void f() {
// FILE *F = fopen("a.txt");
// if (rand()) // state split
// return; // warning
// } // warning
// While this isn't necessarily true (leaking the same stream could result
// from a different kinds of errors), the reduction in redundant reports
// makes this a worthwhile heuristic.
// FIXME: Add a checker option to turn this uniqueing feature off.
const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C);
assert(StreamOpenNode && "Could not find place of stream opening.");
PathDiagnosticLocation LocUsedForUniqueing;
if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics())
LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
StreamStmt, C.getSourceManager(),
StreamOpenNode->getLocationContext());
std::unique_ptr<PathSensitiveBugReport> R =
std::make_unique<PathSensitiveBugReport>(
BT_ResourceLeak,
"Opened stream never closed. Potential resource leak.", Err,
LocUsedForUniqueing,
StreamOpenNode->getLocationContext()->getDecl());
R->markInteresting(LeakSym);
C.emitReport(std::move(R));
}
return Err;
}
void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
llvm::SmallVector<SymbolRef, 2> LeakedSyms;
const StreamMapTy &Map = State->get<StreamMap>();
for (const auto &I : Map) {
SymbolRef Sym = I.first;
const StreamState &SS = I.second;
if (!SymReaper.isDead(Sym))
continue;
if (SS.isOpened())
LeakedSyms.push_back(Sym);
State = State->remove<StreamMap>(Sym);
}
ExplodedNode *N = C.getPredecessor();
if (!LeakedSyms.empty())
N = reportLeaks(LeakedSyms, C, N);
C.addTransition(State, N);
}
ProgramStateRef StreamChecker::checkPointerEscape(
ProgramStateRef State, const InvalidatedSymbols &Escaped,
const CallEvent *Call, PointerEscapeKind Kind) const {
// Check for file-handling system call that is not handled by the checker.
// FIXME: The checker should be updated to handle all system calls that take
// 'FILE*' argument. These are now ignored.
if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader())
return State;
for (SymbolRef Sym : Escaped) {
// The symbol escaped.
// From now the stream can be manipulated in unknown way to the checker,
// it is not possible to handle it any more.
// Optimistically, assume that the corresponding file handle will be closed
// somewhere else.
// Remove symbol from state so the following stream calls on this symbol are
// not handled by the checker.
State = State->remove<StreamMap>(Sym);
}
return State;
}
//===----------------------------------------------------------------------===//
// Checker registration.
//===----------------------------------------------------------------------===//
void ento::registerStreamChecker(CheckerManager &Mgr) {
auto *Checker = Mgr.registerChecker<StreamChecker>();
Checker->PedanticMode =
Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "Pedantic");
}
bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {
return true;
}
void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
auto *Checker = Mgr.getChecker<StreamChecker>();
Checker->TestMode = true;
}
bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
return true;
}