diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -64,6 +64,7 @@ CallDescriptionMap Callbacks = { {{"fopen"}, &StreamChecker::evalFopen}, + {{"freopen", 3}, &StreamChecker::evalFreopen}, {{"tmpfile"}, &StreamChecker::evalFopen}, {{"fclose", 1}, &StreamChecker::evalFclose}, {{"fread", 4}, @@ -90,6 +91,7 @@ }; void evalFopen(const CallEvent &Call, CheckerContext &C) const; + void evalFreopen(const CallEvent &Call, CheckerContext &C) const; void evalFclose(const CallEvent &Call, CheckerContext &C) const; void evalFseek(const CallEvent &Call, CheckerContext &C) const; @@ -160,6 +162,49 @@ C.addTransition(stateNull); } +void StreamChecker::evalFreopen(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + Optional StreamVal = Call.getArgSVal(2).getAs(); + if (!StreamVal) + return; + // Do not allow NULL as passed stream pointer. + // This is not specified in the man page but may crash on some system. + checkNullStream(*StreamVal, C, State); + // Check if error was generated. + if (C.isDifferent()) + return; + + SymbolRef StreamSym = StreamVal->getAsSymbol(); + // Do not care about special values for stream ("(FILE *)0x12345"?). + if (!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().makeNull()); + + StateRetNotNull = + StateRetNotNull->set(StreamSym, StreamState::getOpened()); + StateRetNull = + StateRetNull->set(StreamSym, StreamState::getOpenFailed()); + + C.addTransition(StateRetNotNull); + C.addTransition(StateRetNull); +} + void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); if (checkDoubleClose(Call, C, State)) diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c --- a/clang/test/Analysis/stream.c +++ b/clang/test/Analysis/stream.c @@ -20,6 +20,7 @@ extern int feof(FILE *stream); extern int ferror(FILE *stream); extern int fileno(FILE *stream); +extern FILE *freopen(const char *pathname, const char *mode, FILE *stream); void check_fread() { FILE *fp = tmpfile(); @@ -111,6 +112,13 @@ fclose(p); // expected-warning {{Try to close a file Descriptor already closed. Cause undefined behaviour}} } +void f_double_close_alias(void) { + FILE *p1 = fopen("foo", "r"); + FILE *p2 = p1; + fclose(p1); + fclose(p2); // expected-warning {{Try to close a file Descriptor already closed. Cause undefined behaviour}} +} + void f_leak(int c) { FILE *p = fopen("foo.c", "r"); if(c) @@ -134,3 +142,37 @@ void pr8081(FILE *stream, long offset, int whence) { fseek(stream, offset, whence); } + +void check_freopen_1() { + FILE *f1 = freopen("foo.c", "r", (FILE *)0); // expected-warning {{Stream pointer might be NULL}} + f1 = freopen(0, "w", (FILE *)0x123456); // Do not report this as error. +} + +void check_freopen_2() { + FILE *f1 = fopen("foo.c", "r"); + if (f1) { + FILE *f2 = freopen(0, "w", f1); + if (f2) { + // Check if f1 and f2 point to the same stream. + fclose(f1); + fclose(f2); // expected-warning {{Try to close a file Descriptor already closed. Cause undefined behaviour}} + } else { + // Reopen failed. + // f1 points now to a possibly invalid stream but this condition is currently not checked. + // f2 is NULL. + rewind(f1); + rewind(f2); // expected-warning {{Stream pointer might be NULL}} + } + } +} + +void check_freopen_3() { + FILE *f1 = fopen("foo.c", "r"); + if (f1) { + // Unchecked result of freopen. + // The f1 may be invalid after this call (not checked by the checker). + freopen(0, "w", f1); + rewind(f1); + fclose(f1); + } +}