mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 08:36:06 +00:00
2153 lines
80 KiB
C++
2153 lines
80 KiB
C++
//===-- 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 "NoOwnershipChangeVisitor.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#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; }
|
|
|
|
LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); }
|
|
LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &os) const {
|
|
os << "NoError: " << NoError << ", FEof: " << FEof
|
|
<< ", FError: " << 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;
|
|
|
|
StringRef getKindStr() const {
|
|
switch (State) {
|
|
case Opened:
|
|
return "Opened";
|
|
case Closed:
|
|
return "Closed";
|
|
case OpenFailed:
|
|
return "OpenFailed";
|
|
}
|
|
llvm_unreachable("Unknown StreamState!");
|
|
}
|
|
|
|
/// 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};
|
|
}
|
|
|
|
LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); }
|
|
LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &os) const;
|
|
|
|
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;
|
|
};
|
|
|
|
LLVM_DUMP_METHOD void StreamState::dumpToStream(llvm::raw_ostream &os) const {
|
|
os << "{Kind: " << getKindStr() << ", Last operation: " << LastOperation
|
|
<< ", ErrorState: ";
|
|
ErrorState.dumpToStream(os);
|
|
os << ", FilePos: " << (FilePositionIndeterminate ? "Indeterminate" : "OK")
|
|
<< '}';
|
|
}
|
|
|
|
/// 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,
|
|
check::ASTDecl<TranslationUnitDecl>> {
|
|
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;
|
|
|
|
/// Finds the declarations of 'FILE *stdin, *stdout, *stderr'.
|
|
void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &,
|
|
BugReporter &) const;
|
|
|
|
const BugType *getBT_StreamEof() const { return &BT_StreamEof; }
|
|
const BugType *getBT_IndeterminatePosition() const {
|
|
return &BT_IndeterminatePosition;
|
|
}
|
|
|
|
/// Assumes that the result of 'fopen' can't alias with the pointee of
|
|
/// 'stdin', 'stdout' or 'stderr'.
|
|
ProgramStateRef assumeNoAliasingWithStdStreams(ProgramStateRef State,
|
|
DefinedSVal RetVal,
|
|
CheckerContext &C) const;
|
|
|
|
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;
|
|
|
|
const CallDescription FCloseDesc = {CDM::CLibrary, {"fclose"}, 1};
|
|
|
|
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}},
|
|
{FCloseDesc, {&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;
|
|
|
|
mutable const VarDecl *StdinDecl = nullptr;
|
|
mutable const VarDecl *StdoutDecl = nullptr;
|
|
mutable const VarDecl *StderrDecl = nullptr;
|
|
|
|
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(const Preprocessor &PP) const {
|
|
if (EofVal)
|
|
return;
|
|
|
|
if (const std::optional<int> OptInt = tryExpandAsInteger("EOF", PP))
|
|
EofVal = *OptInt;
|
|
else
|
|
EofVal = -1;
|
|
if (const std::optional<int> OptInt = tryExpandAsInteger("SEEK_SET", PP))
|
|
SeekSetVal = *OptInt;
|
|
if (const std::optional<int> OptInt = tryExpandAsInteger("SEEK_END", PP))
|
|
SeekEndVal = *OptInt;
|
|
if (const std::optional<int> OptInt = tryExpandAsInteger("SEEK_CUR", PP))
|
|
SeekCurVal = *OptInt;
|
|
}
|
|
|
|
/// 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
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Definition of NoStreamStateChangeVisitor.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
class NoStreamStateChangeVisitor final : public NoOwnershipChangeVisitor {
|
|
protected:
|
|
/// Syntactically checks whether the callee is a closing function. Since
|
|
/// we have no path-sensitive information on this call (we would need a
|
|
/// CallEvent instead of a CallExpr for that), its possible that a
|
|
/// closing function was called indirectly through a function pointer,
|
|
/// but we are not able to tell, so this is a best effort analysis.
|
|
bool isClosingCallAsWritten(const CallExpr &Call) const {
|
|
const auto *StreamChk = static_cast<const StreamChecker *>(&Checker);
|
|
return StreamChk->FCloseDesc.matchesAsWritten(Call);
|
|
}
|
|
|
|
bool doesFnIntendToHandleOwnership(const Decl *Callee,
|
|
ASTContext &ACtx) final {
|
|
const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
|
|
|
|
// Given that the stack frame was entered, the body should always be
|
|
// theoretically obtainable. In case of body farms, the synthesized body
|
|
// is not attached to declaration, thus triggering the '!FD->hasBody()'
|
|
// branch. That said, would a synthesized body ever intend to handle
|
|
// ownership? As of today they don't. And if they did, how would we
|
|
// put notes inside it, given that it doesn't match any source locations?
|
|
if (!FD || !FD->hasBody())
|
|
return false;
|
|
using namespace clang::ast_matchers;
|
|
|
|
auto Matches =
|
|
match(findAll(callExpr().bind("call")), *FD->getBody(), ACtx);
|
|
for (BoundNodes Match : Matches) {
|
|
if (const auto *Call = Match.getNodeAs<CallExpr>("call"))
|
|
if (isClosingCallAsWritten(*Call))
|
|
return true;
|
|
}
|
|
// TODO: Ownership might change with an attempt to store stream object, not
|
|
// only through closing it. Check for attempted stores as well.
|
|
return false;
|
|
}
|
|
|
|
bool hasResourceStateChanged(ProgramStateRef CallEnterState,
|
|
ProgramStateRef CallExitEndState) final {
|
|
return CallEnterState->get<StreamMap>(Sym) !=
|
|
CallExitEndState->get<StreamMap>(Sym);
|
|
}
|
|
|
|
PathDiagnosticPieceRef emitNote(const ExplodedNode *N) override {
|
|
PathDiagnosticLocation L = PathDiagnosticLocation::create(
|
|
N->getLocation(),
|
|
N->getState()->getStateManager().getContext().getSourceManager());
|
|
return std::make_shared<PathDiagnosticEventPiece>(
|
|
L, "Returning without closing stream object or storing it for later "
|
|
"release");
|
|
}
|
|
|
|
public:
|
|
NoStreamStateChangeVisitor(SymbolRef Sym, const StreamChecker *Checker)
|
|
: NoOwnershipChangeVisitor(Sym, Checker) {}
|
|
};
|
|
|
|
} // 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 std::optional<int64_t> getKnownValue(ProgramStateRef State, SVal V) {
|
|
SValBuilder &SVB = State->getStateManager().getSValBuilder();
|
|
if (const llvm::APSInt *Int = SVB.getKnownValue(State, V))
|
|
return Int->tryExtValue();
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// Invalidate only the requested elements instead of the whole buffer.
|
|
/// This is basically a refinement of the more generic 'escapeArgs' or
|
|
/// the plain old 'invalidateRegions'.
|
|
static ProgramStateRef
|
|
escapeByStartIndexAndCount(ProgramStateRef State, const CallEvent &Call,
|
|
unsigned BlockCount, const SubRegion *Buffer,
|
|
QualType ElemType, int64_t StartIndex,
|
|
int64_t ElementCount) {
|
|
constexpr auto DoNotInvalidateSuperRegion =
|
|
RegionAndSymbolInvalidationTraits::InvalidationKinds::
|
|
TK_DoNotInvalidateSuperRegion;
|
|
|
|
const LocationContext *LCtx = Call.getLocationContext();
|
|
const ASTContext &Ctx = State->getStateManager().getContext();
|
|
SValBuilder &SVB = State->getStateManager().getSValBuilder();
|
|
auto &RegionManager = Buffer->getMemRegionManager();
|
|
|
|
SmallVector<SVal> EscapingVals;
|
|
EscapingVals.reserve(ElementCount);
|
|
|
|
RegionAndSymbolInvalidationTraits ITraits;
|
|
for (auto Idx : llvm::seq(StartIndex, StartIndex + ElementCount)) {
|
|
NonLoc Index = SVB.makeArrayIndex(Idx);
|
|
const auto *Element =
|
|
RegionManager.getElementRegion(ElemType, Index, Buffer, Ctx);
|
|
EscapingVals.push_back(loc::MemRegionVal(Element));
|
|
ITraits.setTrait(Element, DoNotInvalidateSuperRegion);
|
|
}
|
|
return State->invalidateRegions(
|
|
EscapingVals, Call.getOriginExpr(), BlockCount, LCtx,
|
|
/*CausesPointerEscape=*/false,
|
|
/*InvalidatedSymbols=*/nullptr, &Call, &ITraits);
|
|
}
|
|
|
|
static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
|
|
const CallEvent &Call,
|
|
ArrayRef<unsigned int> EscapingArgs) {
|
|
auto GetArgSVal = [&Call](int Idx) { return Call.getArgSVal(Idx); };
|
|
auto EscapingVals = to_vector(map_range(EscapingArgs, GetArgSVal));
|
|
State = State->invalidateRegions(EscapingVals, Call.getOriginExpr(),
|
|
C.blockCount(), C.getLocationContext(),
|
|
/*CausesPointerEscape=*/false,
|
|
/*InvalidatedSymbols=*/nullptr);
|
|
return State;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Methods of StreamChecker.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void StreamChecker::checkPreCall(const CallEvent &Call,
|
|
CheckerContext &C) const {
|
|
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();
|
|
}
|
|
|
|
ProgramStateRef StreamChecker::assumeNoAliasingWithStdStreams(
|
|
ProgramStateRef State, DefinedSVal RetVal, CheckerContext &C) const {
|
|
auto assumeRetNE = [&C, RetVal](ProgramStateRef State,
|
|
const VarDecl *Var) -> ProgramStateRef {
|
|
if (!Var)
|
|
return State;
|
|
const auto *LCtx = C.getLocationContext();
|
|
auto &StoreMgr = C.getStoreManager();
|
|
auto &SVB = C.getSValBuilder();
|
|
SVal VarValue = State->getSVal(StoreMgr.getLValueVar(Var, LCtx));
|
|
auto NoAliasState =
|
|
SVB.evalBinOp(State, BO_NE, RetVal, VarValue, SVB.getConditionType())
|
|
.castAs<DefinedOrUnknownSVal>();
|
|
return State->assume(NoAliasState, true);
|
|
};
|
|
|
|
assert(State);
|
|
State = assumeRetNE(State, StdinDecl);
|
|
State = assumeRetNE(State, StdoutDecl);
|
|
State = assumeRetNE(State, StderrDecl);
|
|
assert(State);
|
|
return State;
|
|
}
|
|
|
|
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));
|
|
|
|
StateNotNull = assumeNoAliasingWithStdStreams(StateNotNull, RetVal, C);
|
|
|
|
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);
|
|
}
|
|
|
|
static QualType getPointeeType(const MemRegion *R) {
|
|
if (!R)
|
|
return {};
|
|
if (const auto *ER = dyn_cast<ElementRegion>(R))
|
|
return ER->getElementType();
|
|
if (const auto *TR = dyn_cast<TypedValueRegion>(R))
|
|
return TR->getValueType();
|
|
if (const auto *SR = dyn_cast<SymbolicRegion>(R))
|
|
return SR->getPointeeStaticType();
|
|
return {};
|
|
}
|
|
|
|
static std::optional<NonLoc> getStartIndex(SValBuilder &SVB,
|
|
const MemRegion *R) {
|
|
if (!R)
|
|
return std::nullopt;
|
|
|
|
auto Zero = [&SVB] {
|
|
BasicValueFactory &BVF = SVB.getBasicValueFactory();
|
|
return nonloc::ConcreteInt(BVF.getIntValue(0, /*isUnsigned=*/false));
|
|
};
|
|
|
|
if (const auto *ER = dyn_cast<ElementRegion>(R))
|
|
return ER->getIndex();
|
|
if (isa<TypedValueRegion>(R))
|
|
return Zero();
|
|
if (isa<SymbolicRegion>(R))
|
|
return Zero();
|
|
return std::nullopt;
|
|
}
|
|
|
|
static ProgramStateRef
|
|
tryToInvalidateFReadBufferByElements(ProgramStateRef State, CheckerContext &C,
|
|
const CallEvent &Call, NonLoc SizeVal,
|
|
NonLoc NMembVal) {
|
|
// Try to invalidate the individual elements.
|
|
const auto *Buffer =
|
|
dyn_cast_or_null<SubRegion>(Call.getArgSVal(0).getAsRegion());
|
|
|
|
const ASTContext &Ctx = C.getASTContext();
|
|
QualType ElemTy = getPointeeType(Buffer);
|
|
std::optional<SVal> StartElementIndex =
|
|
getStartIndex(C.getSValBuilder(), Buffer);
|
|
|
|
// Drop the outermost ElementRegion to get the buffer.
|
|
if (const auto *ER = dyn_cast_or_null<ElementRegion>(Buffer))
|
|
Buffer = dyn_cast<SubRegion>(ER->getSuperRegion());
|
|
|
|
std::optional<int64_t> CountVal = getKnownValue(State, NMembVal);
|
|
std::optional<int64_t> Size = getKnownValue(State, SizeVal);
|
|
std::optional<int64_t> StartIndexVal =
|
|
getKnownValue(State, StartElementIndex.value_or(UnknownVal()));
|
|
|
|
if (!ElemTy.isNull() && CountVal && Size && StartIndexVal) {
|
|
int64_t NumBytesRead = Size.value() * CountVal.value();
|
|
int64_t ElemSizeInChars = Ctx.getTypeSizeInChars(ElemTy).getQuantity();
|
|
if (ElemSizeInChars == 0 || NumBytesRead < 0)
|
|
return nullptr;
|
|
|
|
bool IncompleteLastElement = (NumBytesRead % ElemSizeInChars) != 0;
|
|
int64_t NumCompleteOrIncompleteElementsRead =
|
|
NumBytesRead / ElemSizeInChars + IncompleteLastElement;
|
|
|
|
constexpr int MaxInvalidatedElementsLimit = 64;
|
|
if (NumCompleteOrIncompleteElementsRead <= MaxInvalidatedElementsLimit) {
|
|
return escapeByStartIndexAndCount(State, Call, C.blockCount(), Buffer,
|
|
ElemTy, *StartIndexVal,
|
|
NumCompleteOrIncompleteElementsRead);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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()) {
|
|
// Try to invalidate the individual elements.
|
|
// Otherwise just fall back to invalidating the whole buffer.
|
|
ProgramStateRef InvalidatedState = tryToInvalidateFReadBufferByElements(
|
|
State, C, Call, *SizeVal, *NMembVal);
|
|
State =
|
|
InvalidatedState ? InvalidatedState : 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() != "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;
|
|
}
|
|
|
|
namespace {
|
|
class StreamClosedVisitor final : public BugReporterVisitor {
|
|
const SymbolRef StreamSym;
|
|
bool Satisfied = false;
|
|
|
|
public:
|
|
explicit StreamClosedVisitor(SymbolRef StreamSym) : StreamSym(StreamSym) {}
|
|
|
|
static void *getTag() {
|
|
static int Tag = 0;
|
|
return &Tag;
|
|
}
|
|
|
|
void Profile(llvm::FoldingSetNodeID &ID) const override {
|
|
ID.AddPointer(getTag());
|
|
ID.AddPointer(StreamSym);
|
|
}
|
|
|
|
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
|
|
BugReporterContext &BRC,
|
|
PathSensitiveBugReport &BR) override {
|
|
if (Satisfied)
|
|
return nullptr;
|
|
const StreamState *PredSS =
|
|
N->getFirstPred()->getState()->get<StreamMap>(StreamSym);
|
|
if (PredSS && PredSS->isClosed())
|
|
return nullptr;
|
|
|
|
const Stmt *S = N->getStmtForDiagnostics();
|
|
if (!S)
|
|
return nullptr;
|
|
Satisfied = true;
|
|
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
|
|
N->getLocationContext());
|
|
llvm::StringLiteral Msg = "Stream is closed here";
|
|
return std::make_shared<PathDiagnosticEventPiece>(Pos, Msg);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
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 .
|
|
if (ExplodedNode *N = C.generateErrorNode()) {
|
|
auto R = std::make_unique<PathSensitiveBugReport>(
|
|
BT_UseAfterClose, "Use of a stream that might be already closed", N);
|
|
R->addVisitor<StreamClosedVisitor>(Sym);
|
|
C.emitReport(std::move(R));
|
|
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);
|
|
R->addVisitor<NoStreamStateChangeVisitor>(LeakSym, this);
|
|
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;
|
|
}
|
|
|
|
static const VarDecl *
|
|
getGlobalStreamPointerByName(const TranslationUnitDecl *TU, StringRef VarName) {
|
|
ASTContext &Ctx = TU->getASTContext();
|
|
const auto &SM = Ctx.getSourceManager();
|
|
const QualType FileTy = Ctx.getFILEType();
|
|
|
|
if (FileTy.isNull())
|
|
return nullptr;
|
|
|
|
const QualType FilePtrTy = Ctx.getPointerType(FileTy).getCanonicalType();
|
|
|
|
auto LookupRes = TU->lookup(&Ctx.Idents.get(VarName));
|
|
for (const Decl *D : LookupRes) {
|
|
if (auto *VD = dyn_cast_or_null<VarDecl>(D)) {
|
|
if (SM.isInSystemHeader(VD->getLocation()) && VD->hasExternalStorage() &&
|
|
VD->getType().getCanonicalType() == FilePtrTy) {
|
|
return VD;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void StreamChecker::checkASTDecl(const TranslationUnitDecl *TU,
|
|
AnalysisManager &Mgr, BugReporter &) const {
|
|
StdinDecl = getGlobalStreamPointerByName(TU, "stdin");
|
|
StdoutDecl = getGlobalStreamPointerByName(TU, "stdout");
|
|
StderrDecl = getGlobalStreamPointerByName(TU, "stderr");
|
|
VaListType = TU->getASTContext().getBuiltinVaListType().getCanonicalType();
|
|
initMacroValues(Mgr.getPreprocessor());
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// 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;
|
|
}
|