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 @@ -57,38 +57,53 @@ class StreamChecker : public Checker { - CallDescription CD_fopen, CD_tmpfile, CD_fclose, CD_fread, CD_fwrite, - CD_fseek, CD_ftell, CD_rewind, CD_fgetpos, CD_fsetpos, CD_clearerr, - CD_feof, CD_ferror, CD_fileno; mutable std::unique_ptr BT_nullfp, BT_illegalwhence, BT_doubleclose, BT_ResourceLeak; public: - StreamChecker() - : CD_fopen("fopen"), CD_tmpfile("tmpfile"), CD_fclose("fclose", 1), - CD_fread("fread", 4), CD_fwrite("fwrite", 4), CD_fseek("fseek", 3), - CD_ftell("ftell", 1), CD_rewind("rewind", 1), CD_fgetpos("fgetpos", 2), - CD_fsetpos("fsetpos", 2), CD_clearerr("clearerr", 1), - CD_feof("feof", 1), CD_ferror("ferror", 1), CD_fileno("fileno", 1) {} + StreamChecker() = default; bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: - void Fopen(CheckerContext &C, const CallExpr *CE) const; - void Tmpfile(CheckerContext &C, const CallExpr *CE) const; - void Fclose(CheckerContext &C, const CallExpr *CE) const; - void Fread(CheckerContext &C, const CallExpr *CE) const; - void Fwrite(CheckerContext &C, const CallExpr *CE) const; - void Fseek(CheckerContext &C, const CallExpr *CE) const; - void Ftell(CheckerContext &C, const CallExpr *CE) const; - void Rewind(CheckerContext &C, const CallExpr *CE) const; - void Fgetpos(CheckerContext &C, const CallExpr *CE) const; - void Fsetpos(CheckerContext &C, const CallExpr *CE) const; - void Clearerr(CheckerContext &C, const CallExpr *CE) const; - void Feof(CheckerContext &C, const CallExpr *CE) const; - void Ferror(CheckerContext &C, const CallExpr *CE) const; - void Fileno(CheckerContext &C, const CallExpr *CE) const; + typedef void (StreamChecker::*FnCheck)(CheckerContext &, + const CallExpr *) const; + + CallDescriptionMap Callbacks = { + {{CDF_MaybeBuiltin, "fopen"}, &StreamChecker::evalFopen}, + {{CDF_MaybeBuiltin, "tmpfile"}, &StreamChecker::evalTmpfile}, + {{CDF_MaybeBuiltin, "fclose", 1}, &StreamChecker::evalFclose}, + {{CDF_MaybeBuiltin, "fread", 4}, &StreamChecker::evalFread}, + {{CDF_MaybeBuiltin, "fwrite", 4}, &StreamChecker::evalFwrite}, + {{CDF_MaybeBuiltin, "fseek", 3}, &StreamChecker::evalFseek}, + {{CDF_MaybeBuiltin, "ftell", 1}, &StreamChecker::evalFtell}, + {{CDF_MaybeBuiltin, "rewind", 1}, &StreamChecker::evalRewind}, + {{CDF_MaybeBuiltin, "fgetpos", 2}, &StreamChecker::evalFgetpos}, + {{CDF_MaybeBuiltin, "fsetpos", 2}, &StreamChecker::evalFsetpos}, + {{CDF_MaybeBuiltin, "clearerr", 1}, &StreamChecker::evalClearerr}, + {{CDF_MaybeBuiltin, "feof", 1}, &StreamChecker::evalFeof}, + {{CDF_MaybeBuiltin, "ferror", 1}, &StreamChecker::evalFerror}, + {{CDF_MaybeBuiltin, "fileno", 1}, &StreamChecker::evalFileno}, + }; + + FnCheck identifyCall(const CallEvent &Call, CheckerContext &C, + const CallExpr *CE) const; + + void evalFopen(CheckerContext &C, const CallExpr *CE) const; + void evalTmpfile(CheckerContext &C, const CallExpr *CE) const; + void evalFclose(CheckerContext &C, const CallExpr *CE) const; + void evalFread(CheckerContext &C, const CallExpr *CE) const; + void evalFwrite(CheckerContext &C, const CallExpr *CE) const; + void evalFseek(CheckerContext &C, const CallExpr *CE) const; + void evalFtell(CheckerContext &C, const CallExpr *CE) const; + void evalRewind(CheckerContext &C, const CallExpr *CE) const; + void evalFgetpos(CheckerContext &C, const CallExpr *CE) const; + void evalFsetpos(CheckerContext &C, const CallExpr *CE) const; + void evalClearerr(CheckerContext &C, const CallExpr *CE) const; + void evalFeof(CheckerContext &C, const CallExpr *CE) const; + void evalFerror(CheckerContext &C, const CallExpr *CE) const; + void evalFileno(CheckerContext &C, const CallExpr *CE) const; void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; @@ -115,120 +130,58 @@ if (!CE) return false; - if (Call.isCalled(CD_fopen)) { - Fopen(C, CE); - return true; - } - if (Call.isCalled(CD_tmpfile)) { - Tmpfile(C, CE); - return true; - } - if (Call.isCalled(CD_fclose)) { - Fclose(C, CE); - return true; - } - if (Call.isCalled(CD_fread)) { - Fread(C, CE); - return true; - } - if (Call.isCalled(CD_fwrite)) { - Fwrite(C, CE); - return true; - } - if (Call.isCalled(CD_fseek)) { - Fseek(C, CE); - return true; - } - if (Call.isCalled(CD_ftell)) { - Ftell(C, CE); - return true; - } - if (Call.isCalled(CD_rewind)) { - Rewind(C, CE); - return true; - } - if (Call.isCalled(CD_fgetpos)) { - Fgetpos(C, CE); - return true; - } - if (Call.isCalled(CD_fsetpos)) { - Fsetpos(C, CE); - return true; - } - if (Call.isCalled(CD_clearerr)) { - Clearerr(C, CE); - return true; - } - if (Call.isCalled(CD_feof)) { - Feof(C, CE); - return true; - } - if (Call.isCalled(CD_ferror)) { - Ferror(C, CE); - return true; - } - if (Call.isCalled(CD_fileno)) { - Fileno(C, CE); - return true; - } + FnCheck Callback = identifyCall(Call, C, CE); + if (!Callback) + return false; - return false; -} + (this->*Callback)(C, CE); -void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { - OpenFileAux(C, CE); + return true; } -void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { - OpenFileAux(C, CE); -} +StreamChecker::FnCheck StreamChecker::identifyCall(const CallEvent &Call, + CheckerContext &C, + const CallExpr *CE) const { + for (auto I : CE->arguments()) { + QualType T = I->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + return nullptr; + } -void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - SValBuilder &svalBuilder = C.getSValBuilder(); - const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()) - .castAs(); - state = state->BindExpr(CE, C.getLocationContext(), RetVal); + const FnCheck *Callback = Callbacks.lookup(Call); + if (!Callback) + return nullptr; - ConstraintManager &CM = C.getConstraintManager(); - // Bifurcate the state into two: one with a valid FILE* pointer, the other - // with a NULL. - ProgramStateRef stateNotNull, stateNull; - std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); + return *Callback; +} - if (SymbolRef Sym = RetVal.getAsSymbol()) { - // if RetVal is not NULL, set the symbol's state to Opened. - stateNotNull = - stateNotNull->set(Sym,StreamState::getOpened(CE)); - stateNull = - stateNull->set(Sym, StreamState::getOpenFailed(CE)); +void StreamChecker::evalFopen(CheckerContext &C, const CallExpr *CE) const { + OpenFileAux(C, CE); +} - C.addTransition(stateNotNull); - C.addTransition(stateNull); - } +void StreamChecker::evalTmpfile(CheckerContext &C, const CallExpr *CE) const { + OpenFileAux(C, CE); } -void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFclose(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); if (state) C.addTransition(state); } -void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFread(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) return; } -void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFwrite(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) return; } -void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFseek(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C))) return; @@ -254,54 +207,80 @@ } } -void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFtell(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalRewind(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFgetpos(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFsetpos(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalClearerr(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFeof(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFerror(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } -void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFileno(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } +void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { + ProgramStateRef state = C.getState(); + SValBuilder &svalBuilder = C.getSValBuilder(); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + DefinedSVal RetVal = + svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) + .castAs(); + state = state->BindExpr(CE, C.getLocationContext(), RetVal); + + ConstraintManager &CM = C.getConstraintManager(); + // Bifurcate the state into two: one with a valid FILE* pointer, the other + // with a NULL. + ProgramStateRef stateNotNull, stateNull; + std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); + + if (SymbolRef Sym = RetVal.getAsSymbol()) { + // if RetVal is not NULL, set the symbol's state to Opened. + stateNotNull = + stateNotNull->set(Sym, StreamState::getOpened(CE)); + stateNull = stateNull->set(Sym, StreamState::getOpenFailed(CE)); + + C.addTransition(stateNotNull); + C.addTransition(stateNull); + } +} + ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, CheckerContext &C) const { Optional DV = SV.getAs();