Skip to content

Commit ffe7049

Browse files
[clang][analyzer] StreamChecker: Model getc, vfscanf, putc, vfprintf (llvm#82476)
Model `getc` and `putc` as equivalent to `fgetc` and `fputc` respectively. Model `vfscanf` and `vfprintf` as `fscanf` and `fprintf`, except that `vfscanf` can not invalidate the parameters due to the indirection via a `va_list`. Nevertheless, we can still track EOF and errors as for `fscanf`.
1 parent 37daff0 commit ffe7049

File tree

6 files changed

+105
-5
lines changed

6 files changed

+105
-5
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,18 +348,30 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
348348
{{{"fgets"}, 3},
349349
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
350350
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},
351+
{{{"getc"}, 1},
352+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
353+
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
351354
{{{"fputc"}, 2},
352355
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
353356
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
354357
{{{"fputs"}, 2},
355358
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
356359
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
360+
{{{"putc"}, 2},
361+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
362+
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
357363
{{{"fprintf"}},
358364
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
359365
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
366+
{{{"vfprintf"}, 3},
367+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
368+
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
360369
{{{"fscanf"}},
361370
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
362371
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
372+
{{{"vfscanf"}, 3},
373+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
374+
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
363375
{{{"ungetc"}, 2},
364376
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
365377
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
@@ -1038,10 +1050,13 @@ void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
10381050
if (!StateNotFailed)
10391051
return;
10401052

1041-
SmallVector<unsigned int> EscArgs;
1042-
for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))
1043-
EscArgs.push_back(EscArg);
1044-
StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);
1053+
if (auto const *Callee = Call.getCalleeIdentifier();
1054+
!Callee || !Callee->getName().equals("vfscanf")) {
1055+
SmallVector<unsigned int> EscArgs;
1056+
for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))
1057+
EscArgs.push_back(EscArg);
1058+
StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);
1059+
}
10451060

10461061
if (StateNotFailed)
10471062
C.addTransition(StateNotFailed);

clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// suppressed.
66
#pragma clang system_header
77

8-
typedef struct __sFILE {
8+
typedef struct _FILE {
99
unsigned char *_p;
1010
} FILE;
1111
FILE *fopen(const char *restrict, const char *restrict) __asm("_" "fopen" );

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#define restrict /*restrict*/
1111
#endif
1212

13+
typedef struct _FILE FILE;
14+
1315
typedef __builtin_va_list va_list;
1416

1517
#define va_start(ap, param) __builtin_va_start(ap, param)
@@ -21,6 +23,10 @@ int vprintf (const char *restrict format, va_list arg);
2123

2224
int vsprintf (char *restrict s, const char *restrict format, va_list arg);
2325

26+
int vfprintf(FILE *stream, const char *format, va_list ap);
27+
28+
int vfscanf(FILE *stream, const char *format, va_list ap);
29+
2430
int some_library_function(int n, va_list arg);
2531

2632
// No warning from system header.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ int ferror(FILE *stream);
7373
int fileno(FILE *stream);
7474
int fflush(FILE *stream);
7575

76+
77+
int getc(FILE *stream);
78+
7679
size_t strlen(const char *);
7780

7881
char *strcpy(char *restrict, const char *restrict);

clang/test/Analysis/stream-invalidate.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// RUN: -analyzer-checker=debug.ExprInspection
55

66
#include "Inputs/system-header-simulator.h"
7+
#include "Inputs/system-header-simulator-for-valist.h"
78

89
void clang_analyzer_eval(int);
910
void clang_analyzer_dump(int);
@@ -145,3 +146,44 @@ void test_fgetpos() {
145146

146147
fclose(F);
147148
}
149+
150+
void test_fprintf() {
151+
FILE *F1 = tmpfile();
152+
if (!F1)
153+
return;
154+
155+
unsigned a = 42;
156+
char *output = "HELLO";
157+
int r = fprintf(F1, "%s\t%u\n", output, a);
158+
// fprintf does not invalidate any of its input
159+
// 69 is ascii for 'E'
160+
clang_analyzer_dump(a); // expected-warning {{42 S32b}}
161+
clang_analyzer_dump(output[1]); // expected-warning {{69 S32b}}
162+
fclose(F1);
163+
}
164+
165+
int test_vfscanf_inner(const char *fmt, ...) {
166+
FILE *F1 = tmpfile();
167+
if (!F1)
168+
return EOF;
169+
170+
va_list ap;
171+
va_start(ap, fmt);
172+
173+
int r = vfscanf(F1, fmt, ap);
174+
175+
fclose(F1);
176+
va_end(ap);
177+
return r;
178+
}
179+
180+
void test_vfscanf() {
181+
int i = 42;
182+
int j = 43;
183+
int r = test_vfscanf_inner("%d", &i);
184+
if (r != EOF) {
185+
// i gets invalidated by the call to test_vfscanf_inner, not by vfscanf.
186+
clang_analyzer_dump(i); // expected-warning {{conj_$}}
187+
clang_analyzer_dump(j); // expected-warning {{43 S32b}}
188+
}
189+
}

clang/test/Analysis/stream.c

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

33
#include "Inputs/system-header-simulator.h"
4+
#include "Inputs/system-header-simulator-for-valist.h"
45

56
void clang_analyzer_eval(int);
67

@@ -65,12 +66,24 @@ void check_fseek(void) {
6566
fclose(fp);
6667
}
6768

69+
void check_fseeko(void) {
70+
FILE *fp = tmpfile();
71+
fseeko(fp, 0, 0); // expected-warning {{Stream pointer might be NULL}}
72+
fclose(fp);
73+
}
74+
6875
void check_ftell(void) {
6976
FILE *fp = tmpfile();
7077
ftell(fp); // expected-warning {{Stream pointer might be NULL}}
7178
fclose(fp);
7279
}
7380

81+
void check_ftello(void) {
82+
FILE *fp = tmpfile();
83+
ftello(fp); // expected-warning {{Stream pointer might be NULL}}
84+
fclose(fp);
85+
}
86+
7487
void check_rewind(void) {
7588
FILE *fp = tmpfile();
7689
rewind(fp); // expected-warning {{Stream pointer might be NULL}}
@@ -129,6 +142,18 @@ void f_dopen(int fd) {
129142
fclose(F);
130143
}
131144

145+
void f_vfprintf(int fd, va_list args) {
146+
FILE *F = fdopen(fd, "r");
147+
vfprintf(F, "%d", args); // expected-warning {{Stream pointer might be NULL}}
148+
fclose(F);
149+
}
150+
151+
void f_vfscanf(int fd, va_list args) {
152+
FILE *F = fdopen(fd, "r");
153+
vfscanf(F, "%u", args); // expected-warning {{Stream pointer might be NULL}}
154+
fclose(F);
155+
}
156+
132157
void f_seek(void) {
133158
FILE *p = fopen("foo", "r");
134159
if (!p)
@@ -138,6 +163,15 @@ void f_seek(void) {
138163
fclose(p);
139164
}
140165

166+
void f_seeko(void) {
167+
FILE *p = fopen("foo", "r");
168+
if (!p)
169+
return;
170+
fseeko(p, 1, SEEK_SET); // no-warning
171+
fseeko(p, 1, 3); // expected-warning {{The whence argument to fseek() should be SEEK_SET, SEEK_END, or SEEK_CUR}}
172+
fclose(p);
173+
}
174+
141175
void f_double_close(void) {
142176
FILE *p = fopen("foo", "r");
143177
if (!p)

0 commit comments

Comments
 (0)