Skip to content

Commit c45d8c9

Browse files
committed
[clang][analyzer] Add missing stream related functions to StdCLibraryFunctionsChecker.
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 9ca3693 commit c45d8c9

File tree

6 files changed

+190
-70
lines changed

6 files changed

+190
-70
lines changed

clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp

Lines changed: 74 additions & 20 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 =
@@ -2192,6 +2195,16 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
21922195
.ArgConstraint(NotNull(ArgNo(1)))
21932196
.ArgConstraint(NotNull(ArgNo(2))));
21942197

2198+
// FILE *fdopen(int fd, const char *mode);
2199+
addToFunctionSummaryMap(
2200+
"fdopen",
2201+
Signature(ArgTypes{IntTy, ConstCharPtrTy}, RetType{FilePtrTy}),
2202+
Summary(NoEvalCall)
2203+
.Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg)
2204+
.Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg)
2205+
.ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax)))
2206+
.ArgConstraint(NotNull(ArgNo(1))));
2207+
21952208
// int fclose(FILE *stream);
21962209
addToFunctionSummaryMap(
21972210
"fclose", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
@@ -2201,6 +2214,59 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22012214
ErrnoNEZeroIrrelevant, GenericFailureMsg)
22022215
.ArgConstraint(NotNull(ArgNo(0))));
22032216

2217+
std::optional<QualType> Off_tTy = lookupTy("off_t");
2218+
std::optional<RangeInt> Off_tMax = getMaxValue(Off_tTy);
2219+
2220+
// int fgetc(FILE *stream);
2221+
// 'getc' is the same as 'fgetc' but may be a macro
2222+
addToFunctionSummaryMap(
2223+
{"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
2224+
Summary(NoEvalCall)
2225+
.Case({ReturnValueCondition(WithinRange, {{0, UCharRangeMax}})},
2226+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2227+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2228+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2229+
.ArgConstraint(NotNull(ArgNo(0))));
2230+
2231+
// int fputc(int c, FILE *stream);
2232+
// 'putc' is the same as 'fputc' but may be a macro
2233+
addToFunctionSummaryMap(
2234+
{"putc", "fputc"},
2235+
Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}),
2236+
Summary(NoEvalCall)
2237+
.Case({ReturnValueCondition(BO_EQ, ArgNo(0))},
2238+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2239+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2240+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2241+
.ArgConstraint(NotNull(ArgNo(1)))
2242+
.ArgConstraint(
2243+
ArgumentCondition(0, WithinRange, {{0, UCharRangeMax}})));
2244+
2245+
// char *fgets(char *restrict s, int n, FILE *restrict stream);
2246+
addToFunctionSummaryMap(
2247+
"fgets",
2248+
Signature(ArgTypes{CharPtrRestrictTy, IntTy, FilePtrRestrictTy},
2249+
RetType{CharPtrTy}),
2250+
Summary(NoEvalCall)
2251+
.Case({ReturnValueCondition(BO_EQ, ArgNo(0))},
2252+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2253+
.Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg)
2254+
.ArgConstraint(NotNull(ArgNo(0)))
2255+
.ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, IntMax)))
2256+
.ArgConstraint(NotNull(ArgNo(2))));
2257+
2258+
// int fputs(const char *restrict s, FILE *restrict stream);
2259+
addToFunctionSummaryMap(
2260+
"fputs",
2261+
Signature(ArgTypes{ConstCharPtrRestrictTy, FilePtrRestrictTy},
2262+
RetType{IntTy}),
2263+
Summary(NoEvalCall)
2264+
.Case(ReturnsNonnegative, ErrnoMustNotBeChecked, GenericSuccessMsg)
2265+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2266+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2267+
.ArgConstraint(NotNull(ArgNo(0)))
2268+
.ArgConstraint(NotNull(ArgNo(1))));
2269+
22042270
// int ungetc(int c, FILE *stream);
22052271
addToFunctionSummaryMap(
22062272
"ungetc", Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}),
@@ -2220,9 +2286,6 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22202286
0, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))
22212287
.ArgConstraint(NotNull(ArgNo(1))));
22222288

2223-
std::optional<QualType> Off_tTy = lookupTy("off_t");
2224-
std::optional<RangeInt> Off_tMax = getMaxValue(Off_tTy);
2225-
22262289
// int fseek(FILE *stream, long offset, int whence);
22272290
// FIXME: It can be possible to get the 'SEEK_' values (like EOFv) and use
22282291
// these for condition of arg 2.
@@ -2853,15 +2916,6 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
28532916
"pathconf", Signature(ArgTypes{ConstCharPtrTy, IntTy}, RetType{LongTy}),
28542917
Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0))));
28552918

2856-
// FILE *fdopen(int fd, const char *mode);
2857-
// FIXME: Improve for errno modeling.
2858-
addToFunctionSummaryMap(
2859-
"fdopen",
2860-
Signature(ArgTypes{IntTy, ConstCharPtrTy}, RetType{FilePtrTy}),
2861-
Summary(NoEvalCall)
2862-
.ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax)))
2863-
.ArgConstraint(NotNull(ArgNo(1))));
2864-
28652919
// void rewinddir(DIR *dir);
28662920
addToFunctionSummaryMap(
28672921
"rewinddir", Signature(ArgTypes{DirPtrTy}, RetType{VoidTy}),

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
@@ -422,32 +422,6 @@ void error_ftello(void) {
422422
fclose(F);
423423
}
424424

425-
void error_fflush_after_fclose(void) {
426-
FILE *F = tmpfile();
427-
int Ret;
428-
fflush(NULL); // no-warning
429-
if (!F)
430-
return;
431-
if ((Ret = fflush(F)) != 0)
432-
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
433-
fclose(F);
434-
fflush(F); // expected-warning {{Stream might be already closed}}
435-
}
436-
437-
void error_fflush_on_open_failed_stream(void) {
438-
FILE *F = tmpfile();
439-
if (!F) {
440-
fflush(F); // no-warning
441-
return;
442-
}
443-
fclose(F);
444-
}
445-
446-
void error_fflush_on_unknown_stream(FILE *F) {
447-
fflush(F); // no-warning
448-
fclose(F); // no-warning
449-
}
450-
451425
void error_fflush_on_non_null_stream_clear_error_states(void) {
452426
FILE *F0 = tmpfile(), *F1 = tmpfile();
453427
// `fflush` clears a non-EOF stream's error state.

clang/test/Analysis/stream-noopen.c

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,85 @@ 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+
}
68+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
69+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
70+
}
71+
72+
void test_fputc(FILE *F) {
73+
int Ret = fputc('a', F);
74+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
75+
if (Ret != EOF) {
76+
clang_analyzer_eval(Ret == 'a'); // expected-warning {{TRUE}}
77+
if (errno) {} // expected-warning {{undefined}}
78+
} else {
79+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
80+
}
81+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
82+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
83+
}
84+
85+
void test_fgets(char *Buf, int N, FILE *F) {
86+
char *Ret = fgets(Buf, N, F);
87+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
88+
clang_analyzer_eval(Buf != NULL); // expected-warning {{TRUE}}
89+
clang_analyzer_eval(N >= 0); // expected-warning {{TRUE}}
90+
if (Ret == Buf) {
91+
if (errno) {} // expected-warning {{undefined}}
92+
} else {
93+
clang_analyzer_eval(Ret == 0); // expected-warning {{TRUE}}
94+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
95+
}
96+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
97+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
98+
}
99+
100+
void test_fputs(char *Buf, FILE *F) {
101+
int Ret = fputs(Buf, F);
102+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
103+
clang_analyzer_eval(Buf != NULL); // expected-warning {{TRUE}}
104+
if (Ret >= 0) {
105+
if (errno) {} // expected-warning {{undefined}}
106+
} else {
107+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
108+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
109+
}
110+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
111+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
112+
}
113+
114+
void test_ungetc(FILE *F) {
115+
int Ret = ungetc('X', F);
116+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
117+
if (Ret == 'X') {
118+
if (errno) {} // expected-warning {{undefined}}
119+
} else {
120+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
121+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
122+
}
123+
clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
124+
clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
125+
}
126+
127+
void test_ungetc_EOF(FILE *F, int C) {
128+
int Ret = ungetc(EOF, F);
129+
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
130+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
131+
clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
132+
Ret = ungetc(C, F);
133+
if (Ret == EOF) {
134+
clang_analyzer_eval(C == EOF); // expected-warning {{TRUE}}
135+
// expected-warning@-1{{FALSE}}
136+
}
137+
}
138+
60139
void test_fclose(FILE *F) {
61140
int Ret = fclose(F);
62141
clang_analyzer_eval(F != NULL); // expected-warning {{TRUE}}
@@ -138,28 +217,17 @@ void test_rewind(FILE *F) {
138217
rewind(F);
139218
}
140219

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);
220+
void test_fflush(FILE *F) {
221+
errno = 0;
222+
int Ret = fflush(F);
223+
clang_analyzer_eval(F != NULL); // expected-warning{{TRUE}}
224+
// expected-warning@-1{{FALSE}}
160225
if (Ret == EOF) {
161-
clang_analyzer_eval(C == EOF); // expected-warning {{TRUE}}
162-
// expected-warning@-1{{FALSE}}
226+
clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
227+
} else {
228+
clang_analyzer_eval(Ret == 0); // expected-warning{{TRUE}}
229+
clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
230+
// expected-warning@-1{{FALSE}}
163231
}
164232
}
165233

clang/test/Analysis/stream-note.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ void check_note_freopen(void) {
5656

5757
void check_note_fdopen(int fd) {
5858
FILE *F = fdopen(fd, "r"); // expected-note {{Stream opened here}}
59+
// stdargs-note@-1 {{'fdopen' is successful}}
5960
if (!F)
6061
// expected-note@-1 {{'F' is non-null}}
6162
// expected-note@-2 {{Taking false branch}}

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}}
@@ -310,3 +312,24 @@ void check_leak_noreturn_2(void) {
310312
} // expected-warning {{Opened stream never closed. Potential resource leak}}
311313
// FIXME: This warning should be placed at the `return` above.
312314
// See https://reviews.llvm.org/D83120 about details.
315+
316+
void fflush_after_fclose(void) {
317+
FILE *F = tmpfile();
318+
int Ret;
319+
fflush(NULL); // no-warning
320+
if (!F)
321+
return;
322+
if ((Ret = fflush(F)) != 0)
323+
clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
324+
fclose(F);
325+
fflush(F); // expected-warning {{Stream might be already closed}}
326+
}
327+
328+
void fflush_on_open_failed_stream(void) {
329+
FILE *F = tmpfile();
330+
if (!F) {
331+
fflush(F); // no-warning
332+
return;
333+
}
334+
fclose(F);
335+
}

0 commit comments

Comments
 (0)