summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp45
-rw-r--r--clang/test/Analysis/stream.c42
2 files changed, 87 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 7a0dda56328..47099f2afb6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -64,6 +64,7 @@ private:
CallDescriptionMap<FnCheck> Callbacks = {
{{"fopen"}, &StreamChecker::evalFopen},
+ {{"freopen", 3}, &StreamChecker::evalFreopen},
{{"tmpfile"}, &StreamChecker::evalFopen},
{{"fclose", 1}, &StreamChecker::evalFclose},
{{"fread", 4},
@@ -90,6 +91,7 @@ private:
};
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 @@ void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const {
C.addTransition(stateNull);
}
+void StreamChecker::evalFreopen(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return;
+
+ Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>();
+ 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<StreamMap>(StreamSym, StreamState::getOpened());
+ StateRetNull =
+ StateRetNull->set<StreamMap>(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
index 61a97493f53..e1db6780d90 100644
--- a/clang/test/Analysis/stream.c
+++ b/clang/test/Analysis/stream.c
@@ -20,6 +20,7 @@ extern void clearerr(FILE *stream);
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 @@ void f_double_close(void) {
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 pr7831(FILE *fp) {
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);
+ }
+}
OpenPOWER on IntegriCloud