Skip to content

[clang][analyzer] Support 'getdelim' and 'getline' in StreamChecker #78693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 23, 2024
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
8 changes: 5 additions & 3 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1347,9 +1347,11 @@ Improvements
`0954dc3fb921 <https://github.com/llvm/llvm-project/commit/0954dc3fb9214b994623f5306473de075f8e3593>`_)

- Improved the ``alpha.unix.Stream`` checker by modeling more functions
``fputs``, ``fputc``, ``fgets``, ``fgetc``, ``fdopen``, ``ungetc``, ``fflush``
and no not recognize alternative ``fopen`` and ``tmpfile`` implementations.
(`#76776 <https://github.com/llvm/llvm-project/pull/76776>`_,
``fputs``, ``fputc``, ``fgets``, ``fgetc``, ``fdopen``, ``ungetc``, ``fflush``,
``getdelim``, ``getline`` and no not recognize alternative
``fopen`` and ``tmpfile`` implementations.
(`#78693 <https://github.com/llvm/llvm-project/pull/78693>`_,
`#76776 <https://github.com/llvm/llvm-project/pull/76776>`_,
`#74296 <https://github.com/llvm/llvm-project/pull/74296>`_,
`#73335 <https://github.com/llvm/llvm-project/pull/73335>`_,
`#72627 <https://github.com/llvm/llvm-project/pull/72627>`_,
Expand Down
66 changes: 66 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
{{{"ungetc"}, 2},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
{{{"getdelim"}, 4},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
{{{"getline"}, 3},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
{{{"fseek"}, 3},
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
{{{"fseeko"}, 3},
Expand Down Expand Up @@ -348,6 +354,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

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

void preFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
void evalFseek(const FnDescription *Desc, const CallEvent &Call,
Expand Down Expand Up @@ -1014,6 +1023,63 @@ void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
C.addTransition(StateFailed);
}

void StreamChecker::evalGetdelim(const FnDescription *Desc,
const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
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);

// Upon successful completion, the getline() and getdelim() functions shall
// return the number of bytes written into the buffer.
// If the end-of-file indicator for the stream is set, the function shall
// return -1.
// If an error occurs, the function shall return -1 and set 'errno'.

// Add transition for the successful state.
if (OldSS->ErrorState != ErrorFEof) {
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(CE, C.getLocationContext(), RetVal);
SValBuilder &SVB = C.getSValBuilder();
ASTContext &ASTC = C.getASTContext();
auto Cond =
SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(CE->getType()),
SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
if (!Cond)
return;
StateNotFailed = StateNotFailed->assume(*Cond, true);
if (!StateNotFailed)
return;
C.addTransition(StateNotFailed);
}

// Add transition for the failed state.
// If a (non-EOF) error occurs, the resulting value of the file position
// indicator for the stream is indeterminate.
ProgramStateRef StateFailed = bindInt(-1, State, C, CE);
StreamErrorState NewES =
OldSS->ErrorState == ErrorFEof ? ErrorFEof : 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::preFseek(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
Expand Down
3 changes: 3 additions & 0 deletions clang/test/Analysis/Inputs/system-header-simulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ typedef long long __int64_t;
typedef __int64_t __darwin_off_t;
typedef __darwin_off_t fpos_t;
typedef int off_t;
typedef long ssize_t;

typedef struct _FILE FILE;
#define SEEK_SET 0 /* Seek from beginning of file. */
Expand Down Expand Up @@ -55,6 +56,8 @@ char *fgets(char *restrict str, int count, FILE *restrict stream);
int fputc(int ch, FILE *stream);
int fputs(const char *restrict s, FILE *restrict stream);
int ungetc(int c, FILE *stream);
ssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream);
ssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream);
int fseek(FILE *__stream, long int __off, int __whence);
int fseeko(FILE *__stream, off_t __off, int __whence);
long int ftell(FILE *__stream);
Expand Down
44 changes: 44 additions & 0 deletions clang/test/Analysis/stream-error.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,50 @@ void error_ungetc() {
ungetc('A', F); // expected-warning {{Stream might be already closed}}
}

void error_getdelim(char *P, size_t Sz) {
FILE *F = tmpfile();
if (!F)
return;
ssize_t Ret = getdelim(&P, &Sz, '\t', F);
if (Ret >= 0) {
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
} else {
clang_analyzer_eval(Ret == -1); // expected-warning {{TRUE}}
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{TRUE}}
if (feof(F)) {
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
getdelim(&P, &Sz, '\n', F); // expected-warning {{Read function called when stream is in EOF state}}
} else {
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
getdelim(&P, &Sz, '\n', F); // expected-warning {{might be 'indeterminate'}}
}
}
fclose(F);
getdelim(&P, &Sz, '\n', F); // expected-warning {{Stream might be already closed}}
}

void error_getline(char *P, size_t Sz) {
FILE *F = tmpfile();
if (!F)
return;
ssize_t Ret = getline(&P, &Sz, F);
if (Ret >= 0) {
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
} else {
clang_analyzer_eval(Ret == -1); // expected-warning {{TRUE}}
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{TRUE}}
if (feof(F)) {
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
getline(&P, &Sz, F); // expected-warning {{Read function called when stream is in EOF state}}
} else {
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
getline(&P, &Sz, F); // expected-warning {{might be 'indeterminate'}}
}
}
fclose(F);
getline(&P, &Sz, F); // expected-warning {{Stream might be already closed}}
}

void write_after_eof_is_allowed(void) {
FILE *F = tmpfile();
if (!F)
Expand Down
1 change: 0 additions & 1 deletion clang/test/Analysis/taint-tester.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ void getwTest(void) {
int i = getw(stdin); // expected-warning + {{tainted}}
}

typedef long ssize_t;
ssize_t getline(char ** __restrict, size_t * __restrict, FILE * __restrict);
int printf(const char * __restrict, ...);
void free(void *ptr);
Expand Down