Skip to content

Commit e493244

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 f2a2f80 commit e493244

File tree

5 files changed

+189
-61
lines changed

5 files changed

+189
-61
lines changed

clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp

Lines changed: 74 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 =
@@ -2204,6 +2207,16 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22042207
.ArgConstraint(NotNull(ArgNo(1)))
22052208
.ArgConstraint(NotNull(ArgNo(2))));
22062209

2210+
// FILE *fdopen(int fd, const char *mode);
2211+
addToFunctionSummaryMap(
2212+
"fdopen",
2213+
Signature(ArgTypes{IntTy, ConstCharPtrTy}, RetType{FilePtrTy}),
2214+
Summary(NoEvalCall)
2215+
.Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg)
2216+
.Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg)
2217+
.ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax)))
2218+
.ArgConstraint(NotNull(ArgNo(1))));
2219+
22072220
// int fclose(FILE *stream);
22082221
addToFunctionSummaryMap(
22092222
"fclose", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
@@ -2212,6 +2225,59 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22122225
.Case(ReturnsEOF, ErrnoNEZeroIrrelevant, GenericFailureMsg)
22132226
.ArgConstraint(NotNull(ArgNo(0))));
22142227

2228+
std::optional<QualType> Off_tTy = lookupTy("off_t");
2229+
std::optional<RangeInt> Off_tMax = getMaxValue(Off_tTy);
2230+
2231+
// int fgetc(FILE *stream);
2232+
// 'getc' is the same as 'fgetc' but may be a macro
2233+
addToFunctionSummaryMap(
2234+
{"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
2235+
Summary(NoEvalCall)
2236+
.Case({ReturnValueCondition(WithinRange, {{0, UCharRangeMax}})},
2237+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2238+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2239+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2240+
.ArgConstraint(NotNull(ArgNo(0))));
2241+
2242+
// int fputc(int c, FILE *stream);
2243+
// 'putc' is the same as 'fputc' but may be a macro
2244+
addToFunctionSummaryMap(
2245+
{"putc", "fputc"},
2246+
Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}),
2247+
Summary(NoEvalCall)
2248+
.Case({ReturnValueCondition(BO_EQ, ArgNo(0))},
2249+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2250+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2251+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2252+
.ArgConstraint(NotNull(ArgNo(1)))
2253+
.ArgConstraint(
2254+
ArgumentCondition(0, WithinRange, {{0, UCharRangeMax}})));
2255+
2256+
// char *fgets(char *restrict s, int n, FILE *restrict stream);
2257+
addToFunctionSummaryMap(
2258+
"fgets",
2259+
Signature(ArgTypes{CharPtrRestrictTy, IntTy, FilePtrRestrictTy},
2260+
RetType{CharPtrTy}),
2261+
Summary(NoEvalCall)
2262+
.Case({ReturnValueCondition(BO_EQ, ArgNo(0))},
2263+
ErrnoMustNotBeChecked, GenericSuccessMsg)
2264+
.Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg)
2265+
.ArgConstraint(NotNull(ArgNo(0)))
2266+
.ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, IntMax)))
2267+
.ArgConstraint(NotNull(ArgNo(2))));
2268+
2269+
// int fputs(const char *restrict s, FILE *restrict stream);
2270+
addToFunctionSummaryMap(
2271+
"fputs",
2272+
Signature(ArgTypes{ConstCharPtrRestrictTy, FilePtrRestrictTy},
2273+
RetType{IntTy}),
2274+
Summary(NoEvalCall)
2275+
.Case(ReturnsNonnegative, ErrnoMustNotBeChecked, GenericSuccessMsg)
2276+
.Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))},
2277+
ErrnoNEZeroIrrelevant, GenericFailureMsg)
2278+
.ArgConstraint(NotNull(ArgNo(0)))
2279+
.ArgConstraint(NotNull(ArgNo(1))));
2280+
22152281
// int ungetc(int c, FILE *stream);
22162282
addToFunctionSummaryMap(
22172283
"ungetc", Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}),
@@ -2231,9 +2297,6 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
22312297
0, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))
22322298
.ArgConstraint(NotNull(ArgNo(1))));
22332299

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

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: 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.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)