diff --git a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -51,7 +51,7 @@ check::PreCall, check::DeadSymbols, check::PointerEscape> { - CallDescription OpenFn, CloseFn; + CallDescription OpenFn, CloseFn, ReopenFn; std::unique_ptr DoubleCloseBugType; std::unique_ptr LeakBugType; @@ -68,7 +68,7 @@ public: SimpleStreamChecker(); - /// Process fopen. + /// Process fopen and freopen. void checkPostCall(const CallEvent &Call, CheckerContext &C) const; /// Process fclose. void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -103,7 +103,7 @@ } // end anonymous namespace SimpleStreamChecker::SimpleStreamChecker() - : OpenFn("fopen"), CloseFn("fclose", 1) { + : OpenFn("fopen"), CloseFn("fclose", 1), ReopenFn("freopen", 3) { // Initialize the bug types. DoubleCloseBugType.reset( new BugType(this, "Double fclose", "Unix Stream API Error")); @@ -114,22 +114,60 @@ /*SuppressOnSink=*/true)); } +static ProgramStateRef tryBindReopenResult(const CallEvent &Call, + CheckerContext &C, + ProgramStateRef State) { + // Get value of file descriptor input argument. + SymbolRef InputDesc = Call.getArgSVal(2).getAsSymbol(); + if (!InputDesc) + return State; + + // Get the return value. + SymbolRef RetDesc = Call.getReturnValue().getAsSymbol(); + if (!RetDesc) + return State; + + // Bind the input to the return value. + const Expr *E = Call.getOriginExpr(); + if (!E) + return State; + + return State->BindExpr(E, C.getLocationContext(), + C.getSValBuilder().makeSymbolVal(InputDesc)); +} + void SimpleStreamChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.isGlobalCFunction()) return; - if (!Call.isCalled(OpenFn)) - return; + ProgramStateRef State = C.getState(); - // Get the symbolic value corresponding to the file handle. - SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); - if (!FileDesc) + if (Call.isCalled(OpenFn)) { + // Get the symbolic value corresponding to the file handle. + SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); + if (!FileDesc) + return; + + // Generate the next transition (an edge in the exploded graph). + State = State->set(FileDesc, StreamState::getOpened()); + } else if (Call.isCalled(ReopenFn)) { + // Try to tell the analyzer that the return value of 'freopen' is the same + // as argument 2 (if no error occurs). + State = tryBindReopenResult(Call, C, State); + + // Set the state to opened regardless of what is was before. + SymbolRef InputDesc = Call.getArgSVal(2).getAsSymbol(); + if (InputDesc) { + const StreamState *SS = State->get(InputDesc); + if (!SS || !SS->isOpened()) + State = State->set(InputDesc, StreamState::getOpened()); + } + } else { return; + } // Generate the next transition (an edge in the exploded graph). - ProgramStateRef State = C.getState(); - State = State->set(FileDesc, StreamState::getOpened()); C.addTransition(State); } diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h b/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h --- a/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h @@ -9,6 +9,7 @@ unsigned char *_p; } FILE; FILE *fopen(const char * restrict, const char * restrict) __asm("_" "fopen" ); +FILE *freopen(const char *restrict, const char *restrict, FILE *restrict); int fputc(int, FILE *); int fputs(const char * restrict, FILE * restrict) __asm("_" "fputs" ); int fclose(FILE *); diff --git a/clang/test/Analysis/simple-stream-checks.c b/clang/test/Analysis/simple-stream-checks.c --- a/clang/test/Analysis/simple-stream-checks.c +++ b/clang/test/Analysis/simple-stream-checks.c @@ -94,3 +94,40 @@ FILE *fp = fopen("myfile.txt", "w"); fp = 0; } // expected-warning {{Opened file is never closed; potential resource leak}} + +void testReopenAndClose() { + FILE *F = fopen("myfile.txt", "w"); + freopen("myfile1.txt", "w", F); + fclose(F); +} // no-warning + +void testReopenAndNoClose() { + FILE *F = fopen("myfile.txt", "w"); + freopen("myfile1.txt", "w", F); +} // expected-warning {{Opened file is never closed; potential resource leak}} + +void testCloseAndReopen() { + FILE *F = fopen("myfile.txt", "w"); + fclose(F); + freopen("myfile1.txt", "w", F); +} // expected-warning {{Opened file is never closed; potential resource leak}} + +void testCloseAndReopenAndClose() { + FILE *F = fopen("myfile.txt", "w"); + fclose(F); + freopen("myfile1.txt", "w", F); + fclose(F); +} // no-warning + +void testBindingOfReopenReturnValue() { + FILE *F = fopen("myfile.txt", "w"); + FILE *F1 = freopen("", "r", F); + fclose(F); + fclose(F1); // expected-warning {{Closing a previously closed file stream}} +} + +void testEscapeAfterReopen() { + FILE *F = fopen("myfile.txt", "w"); + FILE *F1 = freopen("", "r", F); + myfclose(F); +} // no-warning