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 @@ -34,7 +34,7 @@ bool isOpened() const { return K == Opened; } bool isClosed() const { return K == Closed; } - //bool isOpenFailed() const { return K == OpenFailed; } + bool isOpenFailed() const { return K == OpenFailed; } //bool isEscaped() const { return K == Escaped; } bool operator==(const StreamState &X) const { return K == X.K; } @@ -50,66 +50,81 @@ }; class StreamChecker; - -using FnCheck = std::function; +using ArgNoTy = unsigned int; +static const ArgNoTy ArgNone = std::numeric_limits::max(); + struct FnDescription { + FnCheck PreFn; FnCheck EvalFn; + ArgNoTy StreamArgNo; }; -class StreamChecker : public Checker { mutable std::unique_ptr BT_nullfp, BT_illegalwhence, - BT_doubleclose, BT_ResourceLeak; + BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak; public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: - CallDescriptionMap FnDescriptions = { - {{"fopen"}, {&StreamChecker::evalFopen}}, - {{"freopen", 3}, {&StreamChecker::evalFreopen}}, - {{"tmpfile"}, {&StreamChecker::evalFopen}}, - {{"fclose", 1}, {&StreamChecker::evalFclose}}, - {{"fread", 4}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}}, - {{"fwrite", 4}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}}, - {{"fseek", 3}, {&StreamChecker::evalFseek}}, - {{"ftell", 1}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"rewind", 1}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"fgetpos", 2}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"fsetpos", 2}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"clearerr", 1}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"feof", 1}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"ferror", 1}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, - {{"fileno", 1}, - {std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}}, + {{"fopen"}, {{}, &StreamChecker::evalFopen, ArgNone}}, + {{"freopen", 3}, + {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, + {{"tmpfile"}, {{}, &StreamChecker::evalFopen, ArgNone}}, + {{"fclose", 1}, + {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, + {{"fread", 4}, {&StreamChecker::preDefault, {}, 3}}, + {{"fwrite", 4}, {&StreamChecker::preDefault, {}, 3}}, + {{"fseek", 3}, {&StreamChecker::preFseek, {}}}, + {{"ftell", 1}, {&StreamChecker::preDefault, {}, 0}}, + {{"rewind", 1}, {&StreamChecker::preDefault, {}, 0}}, + {{"fgetpos", 2}, {&StreamChecker::preDefault, {}, 0}}, + {{"fsetpos", 2}, {&StreamChecker::preDefault, {}, 0}}, + {{"clearerr", 1}, {&StreamChecker::preDefault, {}, 0}}, + {{"feof", 1}, {&StreamChecker::preDefault, {}, 0}}, + {{"ferror", 1}, {&StreamChecker::preDefault, {}, 0}}, + {{"fileno", 1}, {&StreamChecker::preDefault, {}, 0}}, }; - 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; - void checkArgNullStream(const CallEvent &Call, CheckerContext &C, - unsigned ArgI) const; - - ProgramStateRef checkNullStream(SVal SV, CheckerContext &C, - ProgramStateRef State) const; - ProgramStateRef checkFseekWhence(SVal SV, CheckerContext &C, - ProgramStateRef State) const; - ProgramStateRef checkDoubleClose(const CallEvent &Call, CheckerContext &C, - ProgramStateRef State) const; + void preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void preFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + + void evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void evalFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + + ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C, ProgramStateRef State) const; + ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const; + ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, + ProgramStateRef State) const; + + const FnDescription *lookupFn(const CallEvent &Call) const { + // Recognize "global C functions" with only integral or pointer arguments + // (and matching name) as stream functions. + if (!Call.isGlobalCFunction()) + return nullptr; + for (auto P : Call.parameters()) { + QualType T = P->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + return nullptr; + } + // Ensure that not a member function is called. + const auto *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD || FD->getKind() != Decl::Function) + return nullptr; + + return FnDescriptions.lookup(Call); + } + }; } // end anonymous namespace @@ -117,31 +132,66 @@ REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) -bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *FD = dyn_cast_or_null(Call.getDecl()); - if (!FD || FD->getKind() != Decl::Function) - return false; +void StreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + const FnDescription *Desc = lookupFn(Call); + if (!Desc) + return; - // Recognize "global C functions" with only integral or pointer arguments - // (and matching name) as stream functions. - if (!Call.isGlobalCFunction()) - return false; - for (auto P : Call.parameters()) { - QualType T = P->getType(); - if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) - return false; - } + (Desc->PreFn)(this, Desc, Call, C); +} - const FnDescription *Description = FnDescriptions.lookup(Call); - if (!Description) +bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + const FnDescription *Desc = lookupFn(Call); + if (!Desc) return false; - (Description->EvalFn)(this, Call, C); + (Desc->EvalFn)(this, Desc, Call, C); return C.isDifferent(); } -void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const { + +void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = Call.getArgSVal(Desc->StreamArgNo); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) + return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + + C.addTransition(State); +} + +void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = Call.getArgSVal(Desc->StreamArgNo); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) + return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); + if (!State) + return; + + C.addTransition(State); +} + +void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + // Do not allow NULL as passed stream pointer. + // This is not specified in the man page but may crash on some system. + State = ensureStreamNonNull(Call.getArgSVal(Desc->StreamArgNo), C, State); + if (!State) + return; + + C.addTransition(State); +} + +void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); SValBuilder &SVB = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); @@ -170,7 +220,7 @@ C.addTransition(StateNull); } -void StreamChecker::evalFreopen(const CallEvent &Call, +void StreamChecker::evalFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -181,14 +231,9 @@ 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. - State = checkNullStream(*StreamVal, C, State); - if (!State) - return; SymbolRef StreamSym = StreamVal->getAsSymbol(); - // Do not care about special values for stream ("(FILE *)0x12345"?). + // Do not care about concrete values for stream ("(FILE *)0x12345"?). if (!StreamSym) return; @@ -212,49 +257,34 @@ C.addTransition(StateRetNull); } -void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { +void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); - State = checkDoubleClose(Call, C, State); - if (State) - C.addTransition(State); -} - -void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { - const Expr *AE2 = Call.getArgExpr(2); - if (!AE2) + SymbolRef Sym = Call.getArgSVal(Desc->StreamArgNo).getAsSymbol(); + if (!Sym) return; - ProgramStateRef State = C.getState(); - - State = checkNullStream(Call.getArgSVal(0), C, State); - if (!State) + const StreamState *SS = State->get(Sym); + if (!SS) return; - State = - checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); - if (!State) - return; + // Close the File Descriptor. + // Regardless if the close fails or not, stream becomes "closed" + // and can not be used any more. + State = State->set(Sym, StreamState::getClosed()); C.addTransition(State); } -void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, - unsigned ArgI) const { - ProgramStateRef State = C.getState(); - State = checkNullStream(Call.getArgSVal(ArgI), C, State); - if (State) - C.addTransition(State); -} -ProgramStateRef StreamChecker::checkNullStream(SVal SV, CheckerContext &C, - ProgramStateRef State) const { - Optional DV = SV.getAs(); - if (!DV) +ProgramStateRef StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { + auto Stream = StreamVal.getAs(); + if (!Stream) return State; ConstraintManager &CM = C.getConstraintManager(); + ProgramStateRef StateNotNull, StateNull; - std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); + std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); if (!StateNotNull && StateNull) { if (ExplodedNode *N = C.generateErrorNode(StateNull)) { @@ -271,9 +301,8 @@ } // Check the legality of the 'whence' argument of 'fseek'. -ProgramStateRef StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, - ProgramStateRef State) const { - Optional CI = SV.getAs(); +ProgramStateRef StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const { + Optional CI = WhenceVal.getAs(); if (!CI) return State; @@ -295,38 +324,52 @@ return State; } -ProgramStateRef StreamChecker::checkDoubleClose(const CallEvent &Call, +// Check: +// Using a stream pointer after 'fclose' causes undefined behavior +// according to cppreference.com . +// Using a stream that has failed to open is likely to cause problems, but not +// explicitly mentioned in documentation. +ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { - SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); + SymbolRef Sym = StreamVal.getAsSymbol(); if (!Sym) - return State; + return nullptr; const StreamState *SS = State->get(Sym); - - // If the file stream is not tracked, return. if (!SS) - return State; + return nullptr; - // Check: Double close a File Descriptor could cause undefined behaviour. - // Conforming to man-pages if (SS->isClosed()) { ExplodedNode *N = C.generateErrorNode(); if (N) { - if (!BT_doubleclose) - BT_doubleclose.reset(new BuiltinBug( - this, "Double fclose", "Try to close a file Descriptor already" - " closed. Cause undefined behaviour.")); + if (!BT_UseAfterClose) + BT_UseAfterClose.reset(new BuiltinBug( + this, "Stream used after close", "File descriptor is used after it was closed." + "Cause undefined behaviour.")); C.emitReport(std::make_unique( - *BT_doubleclose, BT_doubleclose->getDescription(), N)); + *BT_UseAfterClose, BT_UseAfterClose->getDescription(), N)); return nullptr; } - return State; } - // Close the File Descriptor. - State = State->set(Sym, StreamState::getClosed()); + if (SS->isOpenFailed()) { + // This should usually not occur because stream pointer is NULL. + // But freopen can cause a state when stream pointer remains non-null but failed to open. + ExplodedNode *N = C.generateErrorNode(); + if (N) { + if (!BT_UseAfterOpenFailed) + BT_UseAfterOpenFailed.reset(new BuiltinBug( + this, "Stream used after failed open", "(Re-)Opening the file descriptor has failed." + "Using it can cause undefined behaviour.")); + C.emitReport(std::make_unique( + *BT_UseAfterOpenFailed, BT_UseAfterOpenFailed->getDescription(), N)); + return nullptr; + } + + return State; + } return State; }