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 @@ -66,6 +66,7 @@ {{"fopen"}, &StreamChecker::evalFopen}, {{"tmpfile"}, &StreamChecker::evalFopen}, {{"fclose", 1}, &StreamChecker::evalFclose}, + {{"freopen", 3}, &StreamChecker::evalFreopen}, {{"fread", 4}, std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, {{"fwrite", 4}, @@ -91,6 +92,7 @@ void evalFopen(const CallEvent &Call, CheckerContext &C) const; void evalFclose(const CallEvent &Call, CheckerContext &C) const; + void evalFreopen(const CallEvent &Call, CheckerContext &C) const; void evalFseek(const CallEvent &Call, CheckerContext &C) const; void checkArgNullStream(const CallEvent &Call, CheckerContext &C, @@ -166,6 +168,59 @@ C.addTransition(State); } +void StreamChecker::evalFreopen(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SValBuilder &SValBuilder = C.getSValBuilder(); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + ConstraintManager &CM = C.getConstraintManager(); + 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; + + DefinedSVal RetVal = + SValBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) + .castAs(); + SymbolRef RetSym = RetVal.getAsSymbol(); + assert(RetSym && "RetVal must be a symbol here."); + + SymbolRef StreamSym = StreamVal->getAsSymbol(); + + // Generate state for NULL return value. + // Set the passed stream to OpenFailed state. + ProgramStateRef StateRetNull = + State->BindExpr(CE, C.getLocationContext(), RetVal); + StateRetNull = CM.assume(StateRetNull, RetVal, false); + assert(StateRetNull && "Assumption on unconstrained value should not fail."); + StateRetNull = + StateRetNull->set(RetSym, StreamState::getOpenFailed()); + if (StreamSym) + StateRetNull = + StateRetNull->set(StreamSym, StreamState::getOpenFailed()); + C.addTransition(StateRetNull); + + // Generate state for non-failed case. + // Return value is the passed stream pointer. + // The stream becomes opened regardless of what is was. + ProgramStateRef StateRetNotNull = + State->BindExpr(CE, C.getLocationContext(), *StreamVal); + if (StreamSym) + StateRetNotNull = + StateRetNotNull->set(StreamSym, StreamState::getOpened()); + C.addTransition(StateRetNotNull); +} + void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { const Expr *AE2 = Call.getArgExpr(2); if (!AE2) 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,34 @@ void pr8081(FILE *stream, long offset, int whence) { fseek(stream, offset, whence); } + +void check_freopen_1() { + FILE *f1 = freopen("foo.c", "r", 0); // expected-warning {{Stream pointer might be NULL}} +} + +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 { + // Open failed, f1 points now to an invalid stream but this condition is currently not checked. + 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); + } +}