Skip to content

[clang][analyzer] Support fflush in the StreamChecker #74296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
{{{"ftell"}, 1},
{&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
{{{"fflush"}, 1},
{&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}},
{{{"rewind"}, 1},
{&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
{{{"fgetpos"}, 2},
Expand Down Expand Up @@ -360,6 +362,12 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
CheckerContext &C,
const StreamErrorState &ErrorKind) const;

void preFflush(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

void evalFflush(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
Expand Down Expand Up @@ -1191,6 +1199,83 @@ void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
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 {
// 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);
}

ProgramStateRef
StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
CheckerContext &C,
Expand Down
1 change: 1 addition & 0 deletions clang/test/Analysis/Inputs/system-header-simulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
int fileno(FILE *stream);
int fflush(FILE *stream);

size_t strlen(const char *);

Expand Down
67 changes: 67 additions & 0 deletions clang/test/Analysis/stream-error.c
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,73 @@ void error_fseek_0(void) {
fclose(F);
}

void error_fflush_after_fclose(void) {
FILE *F = tmpfile();
int Ret;
fflush(NULL); // no-warning
if (!F)
return;
if ((Ret = fflush(F)) != 0)
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
fclose(F);
fflush(F); // expected-warning {{Stream might be already closed}}
}

void error_fflush_on_open_failed_stream(void) {
FILE *F = tmpfile();
if (!F) {
fflush(F); // no-warning
return;
}
fclose(F);
}

void error_fflush_on_unknown_stream(FILE *F) {
fflush(F); // no-warning
fclose(F); // no-warning
}

void error_fflush_on_non_null_stream_clear_error_states(void) {
FILE *F0 = tmpfile(), *F1 = tmpfile();
// `fflush` clears a non-EOF stream's error state.
if (F0) {
StreamTesterChecker_make_ferror_stream(F0);
if (fflush(F0) == 0) { // no-warning
clang_analyzer_eval(ferror(F0)); // expected-warning {{FALSE}}
clang_analyzer_eval(feof(F0)); // expected-warning {{FALSE}}
}
fclose(F0);
}
// `fflush` clears an EOF stream's error state.
if (F1) {
StreamTesterChecker_make_feof_stream(F1);
if (fflush(F1) == 0) { // no-warning
clang_analyzer_eval(ferror(F1)); // expected-warning {{FALSE}}
clang_analyzer_eval(feof(F1)); // expected-warning {{TRUE}}
}
fclose(F1);
}
}

void error_fflush_on_null_stream_clear_error_states(void) {
FILE *F0 = tmpfile(), *F1 = tmpfile();
// `fflush` clears all stream's error states, while retains their EOF states.
if (F0 && F1) {
StreamTesterChecker_make_ferror_stream(F0);
StreamTesterChecker_make_feof_stream(F1);
if (fflush(NULL) == 0) { // no-warning
clang_analyzer_eval(ferror(F0)); // expected-warning {{FALSE}}
clang_analyzer_eval(feof(F0)); // expected-warning {{FALSE}}
clang_analyzer_eval(ferror(F1)); // expected-warning {{FALSE}}
clang_analyzer_eval(feof(F1)); // expected-warning {{TRUE}}
}
}
if (F0)
fclose(F0);
if (F1)
fclose(F1);
}

void error_indeterminate(void) {
FILE *F = fopen("file", "r+");
if (!F)
Expand Down