Skip to content

Commit ea75542

Browse files
authored
[clang][analyzer] Support 'getdelim' and 'getline' in StreamChecker (#78693)
1 parent fcff458 commit ea75542

File tree

5 files changed

+118
-4
lines changed

5 files changed

+118
-4
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,9 +1400,11 @@ Improvements
14001400
`0954dc3fb921 <https://github.com/llvm/llvm-project/commit/0954dc3fb9214b994623f5306473de075f8e3593>`_)
14011401

14021402
- Improved the ``alpha.unix.Stream`` checker by modeling more functions
1403-
``fputs``, ``fputc``, ``fgets``, ``fgetc``, ``fdopen``, ``ungetc``, ``fflush``
1404-
and no not recognize alternative ``fopen`` and ``tmpfile`` implementations.
1405-
(`#76776 <https://github.com/llvm/llvm-project/pull/76776>`_,
1403+
``fputs``, ``fputc``, ``fgets``, ``fgetc``, ``fdopen``, ``ungetc``, ``fflush``,
1404+
``getdelim``, ``getline`` and no not recognize alternative
1405+
``fopen`` and ``tmpfile`` implementations.
1406+
(`#78693 <https://github.com/llvm/llvm-project/pull/78693>`_,
1407+
`#76776 <https://github.com/llvm/llvm-project/pull/76776>`_,
14061408
`#74296 <https://github.com/llvm/llvm-project/pull/74296>`_,
14071409
`#73335 <https://github.com/llvm/llvm-project/pull/73335>`_,
14081410
`#72627 <https://github.com/llvm/llvm-project/pull/72627>`_,

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
272272
{{{"ungetc"}, 2},
273273
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
274274
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
275+
{{{"getdelim"}, 4},
276+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
277+
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
278+
{{{"getline"}, 3},
279+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
280+
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
275281
{{{"fseek"}, 3},
276282
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
277283
{{{"fseeko"}, 3},
@@ -354,6 +360,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
354360
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
355361
CheckerContext &C) const;
356362

363+
void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
364+
CheckerContext &C) const;
365+
357366
void preFseek(const FnDescription *Desc, const CallEvent &Call,
358367
CheckerContext &C) const;
359368
void evalFseek(const FnDescription *Desc, const CallEvent &Call,
@@ -1083,6 +1092,63 @@ void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
10831092
C.addTransition(StateFailed);
10841093
}
10851094

1095+
void StreamChecker::evalGetdelim(const FnDescription *Desc,
1096+
const CallEvent &Call,
1097+
CheckerContext &C) const {
1098+
ProgramStateRef State = C.getState();
1099+
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1100+
if (!StreamSym)
1101+
return;
1102+
1103+
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
1104+
if (!CE)
1105+
return;
1106+
1107+
const StreamState *OldSS = State->get<StreamMap>(StreamSym);
1108+
if (!OldSS)
1109+
return;
1110+
1111+
assertStreamStateOpened(OldSS);
1112+
1113+
// Upon successful completion, the getline() and getdelim() functions shall
1114+
// return the number of bytes written into the buffer.
1115+
// If the end-of-file indicator for the stream is set, the function shall
1116+
// return -1.
1117+
// If an error occurs, the function shall return -1 and set 'errno'.
1118+
1119+
// Add transition for the successful state.
1120+
if (OldSS->ErrorState != ErrorFEof) {
1121+
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
1122+
ProgramStateRef StateNotFailed =
1123+
State->BindExpr(CE, C.getLocationContext(), RetVal);
1124+
SValBuilder &SVB = C.getSValBuilder();
1125+
ASTContext &ASTC = C.getASTContext();
1126+
auto Cond =
1127+
SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(CE->getType()),
1128+
SVB.getConditionType())
1129+
.getAs<DefinedOrUnknownSVal>();
1130+
if (!Cond)
1131+
return;
1132+
StateNotFailed = StateNotFailed->assume(*Cond, true);
1133+
if (!StateNotFailed)
1134+
return;
1135+
C.addTransition(StateNotFailed);
1136+
}
1137+
1138+
// Add transition for the failed state.
1139+
// If a (non-EOF) error occurs, the resulting value of the file position
1140+
// indicator for the stream is indeterminate.
1141+
ProgramStateRef StateFailed = bindInt(-1, State, C, CE);
1142+
StreamErrorState NewES =
1143+
OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError;
1144+
StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
1145+
StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
1146+
if (OldSS->ErrorState != ErrorFEof)
1147+
C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
1148+
else
1149+
C.addTransition(StateFailed);
1150+
}
1151+
10861152
void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
10871153
CheckerContext &C) const {
10881154
ProgramStateRef State = C.getState();

clang/test/Analysis/Inputs/system-header-simulator.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ typedef long long __int64_t;
1414
typedef __int64_t __darwin_off_t;
1515
typedef __darwin_off_t fpos_t;
1616
typedef int off_t;
17+
typedef long ssize_t;
1718

1819
typedef struct _FILE FILE;
1920
#define SEEK_SET 0 /* Seek from beginning of file. */
@@ -55,6 +56,8 @@ char *fgets(char *restrict str, int count, FILE *restrict stream);
5556
int fputc(int ch, FILE *stream);
5657
int fputs(const char *restrict s, FILE *restrict stream);
5758
int ungetc(int c, FILE *stream);
59+
ssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream);
60+
ssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream);
5861
int fseek(FILE *__stream, long int __off, int __whence);
5962
int fseeko(FILE *__stream, off_t __off, int __whence);
6063
long int ftell(FILE *__stream);

clang/test/Analysis/stream-error.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,50 @@ void error_ungetc() {
249249
ungetc('A', F); // expected-warning {{Stream might be already closed}}
250250
}
251251

252+
void error_getdelim(char *P, size_t Sz) {
253+
FILE *F = tmpfile();
254+
if (!F)
255+
return;
256+
ssize_t Ret = getdelim(&P, &Sz, '\t', F);
257+
if (Ret >= 0) {
258+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
259+
} else {
260+
clang_analyzer_eval(Ret == -1); // expected-warning {{TRUE}}
261+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{TRUE}}
262+
if (feof(F)) {
263+
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
264+
getdelim(&P, &Sz, '\n', F); // expected-warning {{Read function called when stream is in EOF state}}
265+
} else {
266+
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
267+
getdelim(&P, &Sz, '\n', F); // expected-warning {{might be 'indeterminate'}}
268+
}
269+
}
270+
fclose(F);
271+
getdelim(&P, &Sz, '\n', F); // expected-warning {{Stream might be already closed}}
272+
}
273+
274+
void error_getline(char *P, size_t Sz) {
275+
FILE *F = tmpfile();
276+
if (!F)
277+
return;
278+
ssize_t Ret = getline(&P, &Sz, F);
279+
if (Ret >= 0) {
280+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
281+
} else {
282+
clang_analyzer_eval(Ret == -1); // expected-warning {{TRUE}}
283+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{TRUE}}
284+
if (feof(F)) {
285+
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
286+
getline(&P, &Sz, F); // expected-warning {{Read function called when stream is in EOF state}}
287+
} else {
288+
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
289+
getline(&P, &Sz, F); // expected-warning {{might be 'indeterminate'}}
290+
}
291+
}
292+
fclose(F);
293+
getline(&P, &Sz, F); // expected-warning {{Stream might be already closed}}
294+
}
295+
252296
void write_after_eof_is_allowed(void) {
253297
FILE *F = tmpfile();
254298
if (!F)

clang/test/Analysis/taint-tester.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ void getwTest(void) {
154154
int i = getw(stdin); // expected-warning + {{tainted}}
155155
}
156156

157-
typedef long ssize_t;
158157
ssize_t getline(char ** __restrict, size_t * __restrict, FILE * __restrict);
159158
int printf(const char * __restrict, ...);
160159
void free(void *ptr);

0 commit comments

Comments
 (0)