Skip to content

Commit 73948ec

Browse files
authored
[clang][analyzer] Support fflush in the StreamChecker (#74296)
1 parent ba4d369 commit 73948ec

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
266266
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
267267
{{{"ftell"}, 1},
268268
{&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
269+
{{{"fflush"}, 1},
270+
{&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}},
269271
{{{"rewind"}, 1},
270272
{&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
271273
{{{"fgetpos"}, 2},
@@ -360,6 +362,12 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
360362
CheckerContext &C,
361363
const StreamErrorState &ErrorKind) const;
362364

365+
void preFflush(const FnDescription *Desc, const CallEvent &Call,
366+
CheckerContext &C) const;
367+
368+
void evalFflush(const FnDescription *Desc, const CallEvent &Call,
369+
CheckerContext &C) const;
370+
363371
/// Check that the stream (in StreamVal) is not NULL.
364372
/// If it can only be NULL a fatal error is emitted and nullptr returned.
365373
/// Otherwise the return value is a new state where the stream is constrained
@@ -1191,6 +1199,84 @@ void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
11911199
C.addTransition(State);
11921200
}
11931201

1202+
void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call,
1203+
CheckerContext &C) const {
1204+
ProgramStateRef State = C.getState();
1205+
SVal StreamVal = getStreamArg(Desc, Call);
1206+
std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
1207+
if (!Stream)
1208+
return;
1209+
1210+
ProgramStateRef StateNotNull, StateNull;
1211+
std::tie(StateNotNull, StateNull) =
1212+
C.getConstraintManager().assumeDual(State, *Stream);
1213+
if (StateNotNull && !StateNull)
1214+
ensureStreamOpened(StreamVal, C, StateNotNull);
1215+
}
1216+
1217+
void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call,
1218+
CheckerContext &C) const {
1219+
ProgramStateRef State = C.getState();
1220+
SVal StreamVal = getStreamArg(Desc, Call);
1221+
std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
1222+
if (!Stream)
1223+
return;
1224+
1225+
// Skip if the stream can be both NULL and non-NULL.
1226+
ProgramStateRef StateNotNull, StateNull;
1227+
std::tie(StateNotNull, StateNull) =
1228+
C.getConstraintManager().assumeDual(State, *Stream);
1229+
if (StateNotNull && StateNull)
1230+
return;
1231+
if (StateNotNull && !StateNull)
1232+
State = StateNotNull;
1233+
else
1234+
State = StateNull;
1235+
1236+
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1237+
if (!CE)
1238+
return;
1239+
1240+
// `fflush` returns EOF on failure, otherwise returns 0.
1241+
ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
1242+
ProgramStateRef StateNotFailed = bindInt(0, State, C, CE);
1243+
1244+
// Clear error states if `fflush` returns 0, but retain their EOF flags.
1245+
auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym,
1246+
const StreamState *SS) {
1247+
if (SS->ErrorState & ErrorFError) {
1248+
StreamErrorState NewES =
1249+
(SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone;
1250+
StreamState NewSS = StreamState::getOpened(Desc, NewES, false);
1251+
StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS);
1252+
}
1253+
};
1254+
1255+
if (StateNotNull && !StateNull) {
1256+
// Skip if the input stream's state is unknown, open-failed or closed.
1257+
if (SymbolRef StreamSym = StreamVal.getAsSymbol()) {
1258+
const StreamState *SS = State->get<StreamMap>(StreamSym);
1259+
if (SS) {
1260+
assert(SS->isOpened() && "Stream is expected to be opened");
1261+
ClearErrorInNotFailed(StreamSym, SS);
1262+
} else
1263+
return;
1264+
}
1265+
} else {
1266+
// Clear error states for all streams.
1267+
const StreamMapTy &Map = StateNotFailed->get<StreamMap>();
1268+
for (const auto &I : Map) {
1269+
SymbolRef Sym = I.first;
1270+
const StreamState &SS = I.second;
1271+
if (SS.isOpened())
1272+
ClearErrorInNotFailed(Sym, &SS);
1273+
}
1274+
}
1275+
1276+
C.addTransition(StateNotFailed);
1277+
C.addTransition(StateFailed);
1278+
}
1279+
11941280
ProgramStateRef
11951281
StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
11961282
CheckerContext &C,

clang/test/Analysis/Inputs/system-header-simulator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ void clearerr(FILE *stream);
6161
int feof(FILE *stream);
6262
int ferror(FILE *stream);
6363
int fileno(FILE *stream);
64+
int fflush(FILE *stream);
6465

6566
size_t strlen(const char *);
6667

clang/test/Analysis/stream-error.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,73 @@ void error_fseek_0(void) {
299299
fclose(F);
300300
}
301301

302+
void error_fflush_after_fclose(void) {
303+
FILE *F = tmpfile();
304+
int Ret;
305+
fflush(NULL); // no-warning
306+
if (!F)
307+
return;
308+
if ((Ret = fflush(F)) != 0)
309+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
310+
fclose(F);
311+
fflush(F); // expected-warning {{Stream might be already closed}}
312+
}
313+
314+
void error_fflush_on_open_failed_stream(void) {
315+
FILE *F = tmpfile();
316+
if (!F) {
317+
fflush(F); // no-warning
318+
return;
319+
}
320+
fclose(F);
321+
}
322+
323+
void error_fflush_on_unknown_stream(FILE *F) {
324+
fflush(F); // no-warning
325+
fclose(F); // no-warning
326+
}
327+
328+
void error_fflush_on_non_null_stream_clear_error_states(void) {
329+
FILE *F0 = tmpfile(), *F1 = tmpfile();
330+
// `fflush` clears a non-EOF stream's error state.
331+
if (F0) {
332+
StreamTesterChecker_make_ferror_stream(F0);
333+
if (fflush(F0) == 0) { // no-warning
334+
clang_analyzer_eval(ferror(F0)); // expected-warning {{FALSE}}
335+
clang_analyzer_eval(feof(F0)); // expected-warning {{FALSE}}
336+
}
337+
fclose(F0);
338+
}
339+
// `fflush` clears an EOF stream's error state.
340+
if (F1) {
341+
StreamTesterChecker_make_feof_stream(F1);
342+
if (fflush(F1) == 0) { // no-warning
343+
clang_analyzer_eval(ferror(F1)); // expected-warning {{FALSE}}
344+
clang_analyzer_eval(feof(F1)); // expected-warning {{TRUE}}
345+
}
346+
fclose(F1);
347+
}
348+
}
349+
350+
void error_fflush_on_null_stream_clear_error_states(void) {
351+
FILE *F0 = tmpfile(), *F1 = tmpfile();
352+
// `fflush` clears all stream's error states, while retains their EOF states.
353+
if (F0 && F1) {
354+
StreamTesterChecker_make_ferror_stream(F0);
355+
StreamTesterChecker_make_feof_stream(F1);
356+
if (fflush(NULL) == 0) { // no-warning
357+
clang_analyzer_eval(ferror(F0)); // expected-warning {{FALSE}}
358+
clang_analyzer_eval(feof(F0)); // expected-warning {{FALSE}}
359+
clang_analyzer_eval(ferror(F1)); // expected-warning {{FALSE}}
360+
clang_analyzer_eval(feof(F1)); // expected-warning {{TRUE}}
361+
}
362+
}
363+
if (F0)
364+
fclose(F0);
365+
if (F1)
366+
fclose(F1);
367+
}
368+
302369
void error_indeterminate(void) {
303370
FILE *F = fopen("file", "r+");
304371
if (!F)

0 commit comments

Comments
 (0)