Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
{{{"fprintf"}},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
{{{"fscanf"}},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
{{{"ungetc"}, 2},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
Expand Down Expand Up @@ -345,6 +348,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

void evalFscanf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

Expand Down Expand Up @@ -975,6 +981,69 @@ void StreamChecker::evalFprintf(const FnDescription *Desc,
C.addTransition(StateFailed);
}

void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
if (Call.getNumArgs() < 2)
return;
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
if (!StreamSym)
return;

const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return;

const StreamState *OldSS = State->get<StreamMap>(StreamSym);
if (!OldSS)
return;

assertStreamStateOpened(OldSS);

SValBuilder &SVB = C.getSValBuilder();
ASTContext &ACtx = C.getASTContext();

// Add the success state.
// In this context "success" means there is not an EOF or other read error
// before any item is matched in 'fscanf'. But there may be match failure,
// therefore return value can be 0 or greater.
// It is not specified what happens if some items (not all) are matched and
// then EOF or read error happens. Now this case is handled like a "success"
// case, and no error flags are set on the stream. This is probably not
// accurate, and the POSIX documentation does not tell more.
if (OldSS->ErrorState != ErrorFEof) {
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(CE, C.getLocationContext(), RetVal);
auto RetGeZero =
SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
if (!RetGeZero)
return;
StateNotFailed = StateNotFailed->assume(*RetGeZero, true);

C.addTransition(StateNotFailed);
}

// Add transition for the failed state.
// Error occurs if nothing is matched yet and reading the input fails.
// Error can be EOF, or other error. At "other error" FERROR or 'errno' can
// be set but it is not further specified if all are required to be set.
// Documentation does not mention, but file position will be set to
// indeterminate similarly as at 'fread'.
ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
? ErrorFEof
: ErrorNone | ErrorFEof | ErrorFError;
StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
if (OldSS->ErrorState != ErrorFEof)
C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
else
C.addTransition(StateFailed);
}

void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
Expand Down
25 changes: 25 additions & 0 deletions clang/test/Analysis/stream-error.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,31 @@ void error_fprintf(void) {
fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
}

void error_fscanf(int *A) {
FILE *F = tmpfile();
if (!F)
return;
int Ret = fscanf(F, "a%ib", A);
if (Ret >= 0) {
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
fscanf(F, "bbb"); // no-warning
} else {
if (ferror(F)) {
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
fscanf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
} else if (feof(F)) {
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
fscanf(F, "bbb"); // expected-warning {{is in EOF state}}
clang_analyzer_eval(feof(F)); // expected-warning {{TRUE}}
} else {
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
fscanf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
}
}
fclose(F);
fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
}

void error_ungetc() {
FILE *F = tmpfile();
if (!F)
Expand Down
6 changes: 6 additions & 0 deletions clang/test/Analysis/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ void check_fprintf(void) {
fclose(fp);
}

void check_fscanf(void) {
FILE *fp = tmpfile();
fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
fclose(fp);
}

void check_ungetc(void) {
FILE *fp = tmpfile();
ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}
Expand Down