[Analyzer][StreamChecker] Added evaluation of fseek.

Summary:
Function `fseek` is now evaluated with setting error return value
and error flags.

Reviewers: Szelethus, NoQ, xazax.hun, rnkovacs, dcoughlin, baloghadamsoftware, martong

Reviewed By: Szelethus

Subscribers: ASDenysPetrov, xazax.hun, baloghadamsoftware, szepet, a.sidorin, mikhail.ramalho, Szelethus, donat.nagy, dkrupp, gamesh411, Charusso, martong, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D75851
This commit is contained in:
Balázs Kéri 2020-04-14 11:14:17 +02:00
parent 09331fd742
commit f2b5e60dfd
2 changed files with 220 additions and 57 deletions

View File

@ -25,8 +25,13 @@ using namespace ento;
namespace {
struct FnDescription;
/// Full state information about a stream pointer.
struct StreamState {
/// The last file operation called in the stream.
const FnDescription *LastOperation;
/// State of a stream symbol.
/// FIXME: We need maybe an "escaped" state later.
enum KindTy {
@ -45,6 +50,9 @@ struct StreamState {
FEof,
/// Other generic (non-EOF) error (`ferror` is true).
FError,
/// Unknown error flag is set (or none), the meaning depends on the last
/// operation.
Unknown
} ErrorState = NoError;
bool isOpened() const { return State == Opened; }
@ -63,28 +71,47 @@ struct StreamState {
assert(State == Opened && "Error undefined for closed stream.");
return ErrorState == FError;
}
bool isUnknown() const {
assert(State == Opened && "Error undefined for closed stream.");
return ErrorState == Unknown;
}
bool operator==(const StreamState &X) const {
// In not opened state error should always NoError.
return State == X.State && ErrorState == X.ErrorState;
return LastOperation == X.LastOperation && State == X.State &&
ErrorState == X.ErrorState;
}
static StreamState getOpened() { return StreamState{Opened}; }
static StreamState getClosed() { return StreamState{Closed}; }
static StreamState getOpenFailed() { return StreamState{OpenFailed}; }
static StreamState getOpenedWithFEof() { return StreamState{Opened, FEof}; }
static StreamState getOpenedWithFError() {
return StreamState{Opened, FError};
static StreamState getOpened(const FnDescription *L) {
return StreamState{L, Opened};
}
static StreamState getOpened(const FnDescription *L, ErrorKindTy E) {
return StreamState{L, Opened, E};
}
static StreamState getClosed(const FnDescription *L) {
return StreamState{L, Closed};
}
static StreamState getOpenFailed(const FnDescription *L) {
return StreamState{L, OpenFailed};
}
/// Return if the specified error kind is possible on the stream in the
/// current state.
/// This depends on the stored `LastOperation` value.
/// If the error is not possible returns empty value.
/// If the error is possible returns the remaining possible error type
/// (after taking out `ErrorKind`). If a single error is possible it will
/// return that value, otherwise unknown error.
Optional<ErrorKindTy> getRemainingPossibleError(ErrorKindTy ErrorKind) const;
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddPointer(LastOperation);
ID.AddInteger(State);
ID.AddInteger(ErrorState);
}
};
class StreamChecker;
struct FnDescription;
using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
const CallEvent &, CheckerContext &)>;
@ -95,8 +122,28 @@ struct FnDescription {
FnCheck PreFn;
FnCheck EvalFn;
ArgNoTy StreamArgNo;
// What errors are possible after this operation.
// Used only if this operation resulted in Unknown state
// (otherwise there is a known single error).
// Must contain 2 or 3 elements, or zero.
llvm::SmallVector<StreamState::ErrorKindTy, 3> PossibleErrors = {};
};
Optional<StreamState::ErrorKindTy>
StreamState::getRemainingPossibleError(ErrorKindTy ErrorKind) const {
assert(ErrorState == Unknown &&
"Function to be used only if error is unknown.");
llvm::SmallVector<StreamState::ErrorKindTy, 3> NewPossibleErrors;
for (StreamState::ErrorKindTy E : LastOperation->PossibleErrors)
if (E != ErrorKind)
NewPossibleErrors.push_back(E);
if (NewPossibleErrors.size() == LastOperation->PossibleErrors.size())
return {};
if (NewPossibleErrors.size() == 1)
return NewPossibleErrors.front();
return Unknown;
}
/// 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) {
@ -115,6 +162,22 @@ DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) {
.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, false));
return State;
}
class StreamChecker
: public Checker<check::PreCall, eval::Call, check::DeadSymbols> {
mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
@ -130,38 +193,49 @@ public:
private:
CallDescriptionMap<FnDescription> FnDescriptions = {
{{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
{{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone, {}}},
{{"freopen", 3},
{&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
{{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
{&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2, {}}},
{{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone, {}}},
{{"fclose", 1},
{&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
{{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3}},
{{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3}},
{{"fseek", 3}, {&StreamChecker::preFseek, nullptr, 0}},
{{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}},
{{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}},
{{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
{{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
{&StreamChecker::preDefault, &StreamChecker::evalFclose, 0, {}}},
{{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}},
{{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}},
{{"fseek", 3},
{&StreamChecker::preFseek,
&StreamChecker::evalFseek,
0,
{StreamState::FEof, StreamState::FError, StreamState::NoError}}},
{{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}},
{{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}},
{{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}},
{{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}},
{{"clearerr", 1},
{&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
{&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0, {}}},
// Note: feof can result in Unknown if at the call there is a
// PossibleErrors with all 3 error states (including NoError).
// Then if feof is false the remaining error could be FError or NoError.
{{"feof", 1},
{&StreamChecker::preDefault,
&StreamChecker::evalFeofFerror<&StreamState::isFEof>, 0}},
&StreamChecker::evalFeofFerror<StreamState::FEof>,
0,
{StreamState::FError, StreamState::NoError}}},
// Note: ferror can result in Unknown if at the call there is a
// PossibleErrors with all 3 error states (including NoError).
// Then if ferror is false the remaining error could be FEof or NoError.
{{"ferror", 1},
{&StreamChecker::preDefault,
&StreamChecker::evalFeofFerror<&StreamState::isFError>, 0}},
{{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}},
&StreamChecker::evalFeofFerror<StreamState::FError>,
0,
{StreamState::FEof, StreamState::NoError}}},
{{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}},
};
CallDescriptionMap<FnDescription> FnTestDescriptions = {
{{"StreamTesterChecker_make_feof_stream", 1},
{nullptr,
&StreamChecker::evalSetFeofFerror<&StreamState::getOpenedWithFEof>, 0}},
{nullptr, &StreamChecker::evalSetFeofFerror<StreamState::FEof>, 0}},
{{"StreamTesterChecker_make_ferror_stream", 1},
{nullptr,
&StreamChecker::evalSetFeofFerror<&StreamState::getOpenedWithFError>,
0}},
{nullptr, &StreamChecker::evalSetFeofFerror<StreamState::FError>, 0}},
};
void evalFopen(const FnDescription *Desc, const CallEvent &Call,
@ -177,6 +251,8 @@ private:
void preFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void preDefault(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
@ -184,11 +260,11 @@ private:
void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
template <bool (StreamState::*IsOfError)() const>
template <StreamState::ErrorKindTy ErrorKind>
void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
template <StreamState (*GetState)()>
template <StreamState::ErrorKindTy EK>
void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
@ -197,7 +273,7 @@ private:
/// Otherwise the return value is a new state where the stream is constrained
/// to be non-null.
ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C,
ProgramStateRef State) const;
ProgramStateRef State) const;
/// Check that the stream is the opened state.
/// If the stream is known to be not opened an error is generated
@ -210,7 +286,7 @@ private:
/// 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;
ProgramStateRef State) const;
/// Find the description data of the function called by a call event.
/// Returns nullptr if no function is recognized.
@ -226,7 +302,7 @@ private:
}
return FnDescriptions.lookup(Call);
}
}
};
} // end anonymous namespace
@ -278,8 +354,10 @@ void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
std::tie(StateNotNull, StateNull) =
C.getConstraintManager().assumeDual(State, RetVal);
StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened());
StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed());
StateNotNull =
StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
StateNull =
StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
C.addTransition(StateNotNull);
C.addTransition(StateNull);
@ -328,9 +406,9 @@ void StreamChecker::evalFreopen(const FnDescription *Desc,
C.getSValBuilder().makeNull());
StateRetNotNull =
StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
StateRetNull =
StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
C.addTransition(StateRetNotNull);
C.addTransition(StateRetNull);
@ -352,7 +430,7 @@ void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
// Close the File Descriptor.
// Regardless if the close fails or not, stream becomes "closed"
// and can not be used any more.
State = State->set<StreamMap>(Sym, StreamState::getClosed());
State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc));
C.addTransition(State);
}
@ -374,6 +452,43 @@ void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
C.addTransition(State);
}
void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
if (!StreamSym)
return;
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return;
// Ignore the call if the stream is not tracked.
if (!State->get<StreamMap>(StreamSym))
return;
DefinedSVal RetVal = makeRetVal(C, CE);
// Make expression result.
State = State->BindExpr(CE, C.getLocationContext(), RetVal);
// Bifurcate the state into failed and non-failed.
// Return zero on success, nonzero on error.
ProgramStateRef StateNotFailed, StateFailed;
std::tie(StateFailed, StateNotFailed) =
C.getConstraintManager().assumeDual(State, RetVal);
// Reset the state to opened with no error.
StateNotFailed =
StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
// We get error.
StateFailed = StateFailed->set<StreamMap>(
StreamSym, StreamState::getOpened(Desc, StreamState::Unknown));
C.addTransition(StateNotFailed);
C.addTransition(StateFailed);
}
void StreamChecker::evalClearerr(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
@ -388,14 +503,11 @@ void StreamChecker::evalClearerr(const FnDescription *Desc,
assertStreamStateOpened(SS);
if (SS->isNoError())
return;
State = State->set<StreamMap>(StreamSym, StreamState::getOpened());
State = State->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
C.addTransition(State);
}
template <bool (StreamState::*IsOfError)() const>
template <StreamState::ErrorKindTy ErrorKind>
void StreamChecker::evalFeofFerror(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
@ -408,25 +520,43 @@ void StreamChecker::evalFeofFerror(const FnDescription *Desc,
if (!CE)
return;
auto AddTransition = [&C, Desc, StreamSym](ProgramStateRef State,
StreamState::ErrorKindTy SSError) {
StreamState SSNew = StreamState::getOpened(Desc, SSError);
State = State->set<StreamMap>(StreamSym, SSNew);
C.addTransition(State);
};
const StreamState *SS = State->get<StreamMap>(StreamSym);
// Ignore the call if the stream is not tracked.
if (!SS)
return;
assertStreamStateOpened(SS);
if ((SS->*IsOfError)()) {
// Function returns nonzero.
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.");
if (!SS->isUnknown()) {
// The stream is in exactly known state (error or not).
// Check if it is in error of kind `ErrorKind`.
if (SS->ErrorState == ErrorKind)
State = bindAndAssumeTrue(State, C, CE);
else
State = bindInt(0, State, C, CE);
// Error state is not changed in the new state.
AddTransition(State, SS->ErrorState);
} else {
// Return zero.
State = State->BindExpr(CE, C.getLocationContext(),
C.getSValBuilder().makeIntVal(0, false));
// Stream is in unknown state, check if error `ErrorKind` is possible.
Optional<StreamState::ErrorKindTy> NewError =
SS->getRemainingPossibleError(ErrorKind);
if (!NewError) {
// This kind of error is not possible, function returns zero.
// Error state remains unknown.
AddTransition(bindInt(0, State, C, CE), StreamState::Unknown);
} else {
// Add state with true returned.
AddTransition(bindAndAssumeTrue(State, C, CE), ErrorKind);
// Add state with false returned and the new remaining error type.
AddTransition(bindInt(0, State, C, CE), *NewError);
}
}
C.addTransition(State);
}
void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
@ -443,14 +573,17 @@ void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
C.addTransition(State);
}
template <StreamState (*GetState)()>
template <StreamState::ErrorKindTy EK>
void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
State = State->set<StreamMap>(StreamSym, (*GetState)());
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, EK));
C.addTransition(State);
}
@ -597,4 +730,3 @@ void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
return true;
}

View File

@ -52,3 +52,34 @@ void stream_error_ferror() {
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
fclose(F);
}
void error_fseek() {
FILE *F = fopen("file", "r");
if (!F)
return;
int rc = fseek(F, 0, SEEK_SET);
if (rc) {
int IsFEof = feof(F), IsFError = ferror(F);
// Get feof or ferror or no error.
clang_analyzer_eval(IsFEof || IsFError);
// expected-warning@-1 {{FALSE}}
// expected-warning@-2 {{TRUE}}
clang_analyzer_eval(IsFEof && IsFError); // expected-warning {{FALSE}}
// Error flags should not change.
if (IsFEof)
clang_analyzer_eval(feof(F)); // expected-warning {{TRUE}}
else
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
if (IsFError)
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
else
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
} else {
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
// Error flags should not change.
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
}
fclose(F);
}