Skip to content

Commit b85fe40

Browse files
authored
[clang][analyzer] Add missing stream related functions to StdLibraryFunctionsChecker. (#76979)
Some stream functions were recently added to `StreamChecker` that were not modeled by `StdCLibraryFunctionsChecker`. To ensure consistency these functions are added to the other checker too. Some of the related tests are re-organized.
1 parent 49ee2ff commit b85fe40

File tree

7 files changed

+221
-64
lines changed

7 files changed

+221
-64
lines changed

clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,13 +2023,6 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
20232023
{{EOFv, EOFv}, {0, UCharRangeMax}},
20242024
"an unsigned char value or EOF")));
20252025

2026-
// The getc() family of functions that returns either a char or an EOF.
2027-
addToFunctionSummaryMap(
2028-
{"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
2029-
Summary(NoEvalCall)
2030-
.Case({ReturnValueCondition(WithinRange,
2031-
{{EOFv, EOFv}, {0, UCharRangeMax}})},
2032-
ErrnoIrrelevant));
20332026
addToFunctionSummaryMap(
20342027
"getchar", Signature(ArgTypes{}, RetType{IntTy}),
20352028
Summary(NoEvalCall)
@@ -2139,7 +2132,17 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
21392132
std::move(GetenvSummary));
21402133
}
21412134

2142-
if (ModelPOSIX) {
2135+
if (!ModelPOSIX) {
2136+
// Without POSIX use of 'errno' is not specified (in these cases).
2137+
// Add these functions without 'errno' checks.
2138+
addToFunctionSummaryMap(
2139+
{"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
2140+
Summary(NoEvalCall)
2141+
.Case({ReturnValueCondition(WithinRange,
2142+
{{EOFv, EOFv}, {0, UCharRangeMax}})},
2143+
ErrnoIrrelevant)
2144+
.ArgConstraint(NotNull(ArgNo(0))));
2145+
} else {
21432146
const auto ReturnsZeroOrMinusOne =
21442147
ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, 0))};
21452148
const auto ReturnsZero =
@@ -2231,6 +2234,63 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22312234
.Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg)
22322235
.ArgConstraint(NotNull(ArgNo(0))));
22332236

2237+
std::optional<QualType> Off_tTy = lookupTy("off_t");
2238+
std::optional<RangeInt> Off_tMax = getMaxValue(Off_tTy);
2239+
2240+
// int fgetc(FILE *stream);
2241+
// 'getc' is the same as 'fgetc' but may be a macro
2242+
addToFunctionSummaryMap(
2243+
{"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
2244+
Summary(NoEvalCall)
2245+
.Case({ReturnValueCondition(WithinRange, {{0, UCharRangeMax}})},
2246+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2247+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2248+
ErrnoIrrelevant, GenericFailureMsg)
2249+
.ArgConstraint(NotNull(ArgNo(0))));
2250+
2251+
// int fputc(int c, FILE *stream);
2252+
// 'putc' is the same as 'fputc' but may be a macro
2253+
addToFunctionSummaryMap(
2254+
{"putc", "fputc"},
2255+
Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}),
2256+
Summary(NoEvalCall)
2257+
.Case({ArgumentCondition(0, WithinRange, Range(0, UCharRangeMax)),
2258+
ReturnValueCondition(BO_EQ, ArgNo(0))},
2259+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2260+
.Case({ArgumentCondition(0, OutOfRange, Range(0, UCharRangeMax)),
2261+
ReturnValueCondition(WithinRange, Range(0, UCharRangeMax))},
2262+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2263+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2264+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2265+
.ArgConstraint(NotNull(ArgNo(1))));
2266+
2267+
// char *fgets(char *restrict s, int n, FILE *restrict stream);
2268+
addToFunctionSummaryMap(
2269+
"fgets",
2270+
Signature(ArgTypes{CharPtrRestrictTy, IntTy, FilePtrRestrictTy},
2271+
RetType{CharPtrTy}),
2272+
Summary(NoEvalCall)
2273+
.Case({ReturnValueCondition(BO_EQ, ArgNo(0))},
2274+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2275+
.Case({IsNull(Ret)}, ErrnoIrrelevant, GenericFailureMsg)
2276+
.ArgConstraint(NotNull(ArgNo(0)))
2277+
.ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, IntMax)))
2278+
.ArgConstraint(
2279+
BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1)))
2280+
.ArgConstraint(NotNull(ArgNo(2))));
2281+
2282+
// int fputs(const char *restrict s, FILE *restrict stream);
2283+
addToFunctionSummaryMap(
2284+
"fputs",
2285+
Signature(ArgTypes{ConstCharPtrRestrictTy, FilePtrRestrictTy},
2286+
RetType{IntTy}),
2287+
Summary(NoEvalCall)
2288+
.Case(ReturnsNonnegative, ErrnoMustNotBeChecked, GenericSuccessMsg)
2289+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2290+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2291+
.ArgConstraint(NotNull(ArgNo(0)))
2292+
.ArgConstraint(NotNull(ArgNo(1))));
2293+
22342294
// int ungetc(int c, FILE *stream);
22352295
addToFunctionSummaryMap(
22362296
"ungetc", Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}),
@@ -2250,9 +2310,6 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22502310
0, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))
22512311
.ArgConstraint(NotNull(ArgNo(1))));
22522312

2253-
std::optional<QualType> Off_tTy = lookupTy("off_t");
2254-
std::optional<RangeInt> Off_tMax = getMaxValue(Off_tTy);
2255-
22562313
// int fseek(FILE *stream, long offset, int whence);
22572314
// FIXME: It can be possible to get the 'SEEK_' values (like EOFv) and use
22582315
// these for condition of arg 2.

clang/test/Analysis/Inputs/std-c-library-functions-POSIX.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ typedef unsigned long int pthread_t;
1111
typedef unsigned long time_t;
1212
typedef unsigned long clockid_t;
1313
typedef __INT64_TYPE__ off64_t;
14+
typedef __INT64_TYPE__ fpos_t;
1415

1516
typedef struct {
1617
int a;
@@ -42,9 +43,22 @@ FILE *fopen(const char *restrict pathname, const char *restrict mode);
4243
FILE *tmpfile(void);
4344
FILE *freopen(const char *restrict pathname, const char *restrict mode,
4445
FILE *restrict stream);
46+
FILE *fdopen(int fd, const char *mode);
4547
int fclose(FILE *stream);
48+
int putc(int c, FILE *stream);
49+
int fputc(int c, FILE *stream);
50+
char *fgets(char *restrict s, int n, FILE *restrict stream);
51+
int fputs(const char *restrict s, FILE *restrict stream);
4652
int fseek(FILE *stream, long offset, int whence);
53+
int fgetpos(FILE *restrict stream, fpos_t *restrict pos);
54+
int fsetpos(FILE *stream, const fpos_t *pos);
55+
int fflush(FILE *stream);
56+
long ftell(FILE *stream);
4757
int fileno(FILE *stream);
58+
void rewind(FILE *stream);
59+
void clearerr(FILE *stream);
60+
int feof(FILE *stream);
61+
int ferror(FILE *stream);
4862
long a64l(const char *str64);
4963
char *l64a(long value);
5064
int open(const char *path, int oflag, ...);
@@ -100,7 +114,6 @@ int pclose(FILE *stream);
100114
int close(int fildes);
101115
long fpathconf(int fildes, int name);
102116
long pathconf(const char *path, int name);
103-
FILE *fdopen(int fd, const char *mode);
104117
void rewinddir(DIR *dir);
105118
void seekdir(DIR *dirp, long loc);
106119
int rand_r(unsigned int *seedp);

clang/test/Analysis/std-c-library-functions-POSIX.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,22 @@
2323
// CHECK: Loaded summary for: FILE *popen(const char *command, const char *type)
2424
// CHECK: Loaded summary for: int fclose(FILE *stream)
2525
// CHECK: Loaded summary for: int pclose(FILE *stream)
26+
// CHECK: Loaded summary for: int getc(FILE *)
27+
// CHECK: Loaded summary for: int fgetc(FILE *)
28+
// CHECK: Loaded summary for: int putc(int c, FILE *stream)
29+
// CHECK: Loaded summary for: int fputc(int c, FILE *stream)
30+
// CHECK: Loaded summary for: char *fgets(char *restrict s, int n, FILE *restrict stream)
31+
// CHECK: Loaded summary for: int fputs(const char *restrict s, FILE *restrict stream)
2632
// CHECK: Loaded summary for: int fseek(FILE *stream, long offset, int whence)
27-
// CHECK: Loaded summary for: int fseeko(FILE *stream, off_t offset, int whence)
28-
// CHECK: Loaded summary for: off_t ftello(FILE *stream)
33+
// CHECK: Loaded summary for: int fgetpos(FILE *restrict stream, fpos_t *restrict pos)
34+
// CHECK: Loaded summary for: int fsetpos(FILE *stream, const fpos_t *pos)
35+
// CHECK: Loaded summary for: int fflush(FILE *stream)
36+
// CHECK: Loaded summary for: long ftell(FILE *stream)
2937
// CHECK: Loaded summary for: int fileno(FILE *stream)
38+
// CHECK: Loaded summary for: void rewind(FILE *stream)
39+
// CHECK: Loaded summary for: void clearerr(FILE *stream)
40+
// CHECK: Loaded summary for: int feof(FILE *stream)
41+
// CHECK: Loaded summary for: int ferror(FILE *stream)
3042
// CHECK: Loaded summary for: long a64l(const char *str64)
3143
// CHECK: Loaded summary for: char *l64a(long value)
3244
// CHECK: Loaded summary for: int open(const char *path, int oflag, ...)

clang/test/Analysis/std-c-library-functions.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@
5353
// CHECK-NEXT: Loaded summary for: int toupper(int)
5454
// CHECK-NEXT: Loaded summary for: int tolower(int)
5555
// CHECK-NEXT: Loaded summary for: int toascii(int)
56-
// CHECK-NEXT: Loaded summary for: int getc(FILE *)
57-
// CHECK-NEXT: Loaded summary for: int fgetc(FILE *)
5856
// CHECK-NEXT: Loaded summary for: int getchar(void)
5957
// CHECK-NEXT: Loaded summary for: unsigned int fread(void *restrict, size_t, size_t, FILE *restrict)
6058
// CHECK-NEXT: Loaded summary for: unsigned int fwrite(const void *restrict, size_t, size_t, FILE *restrict)
@@ -63,6 +61,8 @@
6361
// CHECK-NEXT: Loaded summary for: ssize_t getline(char **restrict, size_t *restrict, FILE *restrict)
6462
// CHECK-NEXT: Loaded summary for: ssize_t getdelim(char **restrict, size_t *restrict, int, FILE *restrict)
6563
// CHECK-NEXT: Loaded summary for: char *getenv(const char *)
64+
// CHECK-NEXT: Loaded summary for: int getc(FILE *)
65+
// CHECK-NEXT: Loaded summary for: int fgetc(FILE *)
6666

6767
#include "Inputs/std-c-library-functions.h"
6868

clang/test/Analysis/stream-error.c

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -491,32 +491,6 @@ void error_ftello(void) {
491491
fclose(F);
492492
}
493493

494-
void error_fflush_after_fclose(void) {
495-
FILE *F = tmpfile();
496-
int Ret;
497-
fflush(NULL); // no-warning
498-
if (!F)
499-
return;
500-
if ((Ret = fflush(F)) != 0)
501-
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
502-
fclose(F);
503-
fflush(F); // expected-warning {{Stream might be already closed}}
504-
}
505-
506-
void error_fflush_on_open_failed_stream(void) {
507-
FILE *F = tmpfile();
508-
if (!F) {
509-
fflush(F); // no-warning
510-
return;
511-
}
512-
fclose(F);
513-
}
514-
515-
void error_fflush_on_unknown_stream(FILE *F) {
516-
fflush(F); // no-warning
517-
fclose(F); // no-warning
518-
}
519-
520494
void error_fflush_on_non_null_stream_clear_error_states(void) {
521495
FILE *F0 = tmpfile(), *F1 = tmpfile();
522496
// `fflush` clears a non-EOF stream's error state.

clang/test/Analysis/stream-noopen.c

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,95 @@ void test_fwrite(FILE *F) {
5757
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
5858
}
5959

60+
void test_fgetc(FILE *F) {
61+
int Ret = fgetc(F);
62+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
63+
if (Ret != EOF) {
64+
if (errno) {} // expected-warning {{undefined}}
65+
} else {
66+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
67+
// expected-warning@-1 {{FALSE}}
68+
}
69+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
70+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
71+
}
72+
73+
void test_fputc(FILE *F) {
74+
int Ret = fputc('a', F);
75+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
76+
if (Ret != EOF) {
77+
clang_analyzer_eval(Ret == 'a'); // expected-warning {{TRUE}}
78+
if (errno) {} // expected-warning {{undefined}}
79+
} else {
80+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
81+
}
82+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
83+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
84+
}
85+
86+
void test_fgets(char *Buf, int N, FILE *F) {
87+
char *Ret = fgets(Buf, N, F);
88+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
89+
clang_analyzer_eval(Buf != NULL); // expected-warning {{TRUE}}
90+
clang_analyzer_eval(N >= 0); // expected-warning {{TRUE}}
91+
if (Ret == Buf) {
92+
if (errno) {} // expected-warning {{undefined}}
93+
} else {
94+
clang_analyzer_eval(Ret == 0); // expected-warning {{TRUE}}
95+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
96+
// expected-warning@-1 {{FALSE}}
97+
}
98+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
99+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
100+
101+
char Buf1[10];
102+
Ret = fgets(Buf1, 11, F); // expected-warning {{The 1st argument to 'fgets' is a buffer with size 10}}
103+
}
104+
105+
void test_fgets_bufsize(FILE *F) {
106+
char Buf[10];
107+
fgets(Buf, 11, F); // expected-warning {{The 1st argument to 'fgets' is a buffer with size 10}}
108+
}
109+
110+
void test_fputs(char *Buf, FILE *F) {
111+
int Ret = fputs(Buf, F);
112+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
113+
clang_analyzer_eval(Buf != NULL); // expected-warning {{TRUE}}
114+
if (Ret >= 0) {
115+
if (errno) {} // expected-warning {{undefined}}
116+
} else {
117+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
118+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
119+
}
120+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
121+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
122+
}
123+
124+
void test_ungetc(FILE *F) {
125+
int Ret = ungetc('X', F);
126+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
127+
if (Ret == 'X') {
128+
if (errno) {} // expected-warning {{undefined}}
129+
} else {
130+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
131+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
132+
}
133+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
134+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
135+
}
136+
137+
void test_ungetc_EOF(FILE *F, int C) {
138+
int Ret = ungetc(EOF, F);
139+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
140+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
141+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
142+
Ret = ungetc(C, F);
143+
if (Ret == EOF) {
144+
clang_analyzer_eval(C == EOF); // expected-warning {{TRUE}}
145+
// expected-warning@-1{{FALSE}}
146+
}
147+
}
148+
60149
void test_fclose(FILE *F) {
61150
int Ret = fclose(F);
62151
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
@@ -138,28 +227,17 @@ void test_rewind(FILE *F) {
138227
rewind(F);
139228
}
140229

141-
void test_ungetc(FILE *F) {
142-
int Ret = ungetc('X', F);
143-
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
144-
if (Ret == 'X') {
145-
if (errno) {} // expected-warning {{undefined}}
146-
} else {
147-
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
148-
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
149-
}
150-
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
151-
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
152-
}
153-
154-
void test_ungetc_EOF(FILE *F, int C) {
155-
int Ret = ungetc(EOF, F);
156-
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
157-
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
158-
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
159-
Ret = ungetc(C, F);
230+
void test_fflush(FILE *F) {
231+
errno = 0;
232+
int Ret = fflush(F);
233+
clang_analyzer_eval(F != NULL); // expected-warning{{TRUE}}
234+
// expected-warning@-1{{FALSE}}
160235
if (Ret == EOF) {
161-
clang_analyzer_eval(C == EOF); // expected-warning {{TRUE}}
162-
// expected-warning@-1{{FALSE}}
236+
clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
237+
} else {
238+
clang_analyzer_eval(Ret == 0); // expected-warning{{TRUE}}
239+
clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
240+
// expected-warning@-1{{FALSE}}
163241
}
164242
}
165243

clang/test/Analysis/stream.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream -verify %s
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
22

33
#include "Inputs/system-header-simulator.h"
44

5+
void clang_analyzer_eval(int);
6+
57
void check_fread(void) {
68
FILE *fp = tmpfile();
79
fread(0, 0, 0, fp); // expected-warning {{Stream pointer might be NULL}}
@@ -316,3 +318,24 @@ void check_leak_noreturn_2(void) {
316318
} // expected-warning {{Opened stream never closed. Potential resource leak}}
317319
// FIXME: This warning should be placed at the `return` above.
318320
// See https://reviews.llvm.org/D83120 about details.
321+
322+
void fflush_after_fclose(void) {
323+
FILE *F = tmpfile();
324+
int Ret;
325+
fflush(NULL); // no-warning
326+
if (!F)
327+
return;
328+
if ((Ret = fflush(F)) != 0)
329+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
330+
fclose(F);
331+
fflush(F); // expected-warning {{Stream might be already closed}}
332+
}
333+
334+
void fflush_on_open_failed_stream(void) {
335+
FILE *F = tmpfile();
336+
if (!F) {
337+
fflush(F); // no-warning
338+
return;
339+
}
340+
fclose(F);
341+
}

0 commit comments

Comments
 (0)