Skip to content

Commit 239312e

Browse files
Reapply "[clang][analyzer] StreamChecker: Model getc, vfscanf, putc, vfprintf" (#83281)
`va_list` is a platform-specific type. On some, it is a struct instead of a pointer to a struct, so `lookupFn` was ignoring calls to `vfprintf` and `vfscanf`. `stream.c` now runs in four different platforms to make sure the logic works across targets.
1 parent e854702 commit 239312e

File tree

6 files changed

+118
-7
lines changed

6 files changed

+118
-7
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,18 +318,30 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
318318
{{{"fgets"}, 3},
319319
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
320320
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},
321+
{{{"getc"}, 1},
322+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
323+
std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},
321324
{{{"fputc"}, 2},
322325
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
323326
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
324327
{{{"fputs"}, 2},
325328
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
326329
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
330+
{{{"putc"}, 2},
331+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
332+
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},
327333
{{{"fprintf"}},
328334
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
329335
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
336+
{{{"vfprintf"}, 3},
337+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
338+
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
330339
{{{"fscanf"}},
331340
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
332341
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
342+
{{{"vfscanf"}, 3},
343+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
344+
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
333345
{{{"ungetc"}, 2},
334346
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
335347
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
@@ -389,6 +401,8 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
389401
mutable int SeekCurVal = 1;
390402
/// Expanded value of SEEK_END, 2 if not found.
391403
mutable int SeekEndVal = 2;
404+
/// The built-in va_list type is platform-specific
405+
mutable QualType VaListType;
392406

393407
void evalFopen(const FnDescription *Desc, const CallEvent &Call,
394408
CheckerContext &C) const;
@@ -518,7 +532,8 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
518532
return nullptr;
519533
for (auto *P : Call.parameters()) {
520534
QualType T = P->getType();
521-
if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
535+
if (!T->isIntegralOrEnumerationType() && !T->isPointerType() &&
536+
T.getCanonicalType() != VaListType)
522537
return nullptr;
523538
}
524539

@@ -557,6 +572,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
557572
SeekCurVal = *OptInt;
558573
}
559574

575+
void initVaListType(CheckerContext &C) const {
576+
VaListType = C.getASTContext().getBuiltinVaListType().getCanonicalType();
577+
}
578+
560579
/// Searches for the ExplodedNode where the file descriptor was acquired for
561580
/// StreamSym.
562581
static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
@@ -705,6 +724,7 @@ static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
705724
void StreamChecker::checkPreCall(const CallEvent &Call,
706725
CheckerContext &C) const {
707726
initMacroValues(C);
727+
initVaListType(C);
708728

709729
const FnDescription *Desc = lookupFn(Call);
710730
if (!Desc || !Desc->PreFn)
@@ -1085,10 +1105,13 @@ void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
10851105
if (!StateNotFailed)
10861106
return;
10871107

1088-
SmallVector<unsigned int> EscArgs;
1089-
for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))
1090-
EscArgs.push_back(EscArg);
1091-
StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);
1108+
if (auto const *Callee = Call.getCalleeIdentifier();
1109+
!Callee || !Callee->getName().equals("vfscanf")) {
1110+
SmallVector<unsigned int> EscArgs;
1111+
for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))
1112+
EscArgs.push_back(EscArg);
1113+
StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);
1114+
}
10921115

10931116
if (StateNotFailed)
10941117
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: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
1+
// RUN: %clang_analyze_cc1 -triple=x86_64-pc-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
2+
// RUN: %clang_analyze_cc1 -triple=armv8-none-linux-eabi -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
3+
// RUN: %clang_analyze_cc1 -triple=aarch64-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
4+
// RUN: %clang_analyze_cc1 -triple=hexagon -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
25

36
#include "Inputs/system-header-simulator.h"
7+
#include "Inputs/system-header-simulator-for-valist.h"
48

59
void clang_analyzer_eval(int);
610

@@ -65,12 +69,24 @@ void check_fseek(void) {
6569
fclose(fp);
6670
}
6771

72+
void check_fseeko(void) {
73+
FILE *fp = tmpfile();
74+
fseeko(fp, 0, 0); // expected-warning {{Stream pointer might be NULL}}
75+
fclose(fp);
76+
}
77+
6878
void check_ftell(void) {
6979
FILE *fp = tmpfile();
7080
ftell(fp); // expected-warning {{Stream pointer might be NULL}}
7181
fclose(fp);
7282
}
7383

84+
void check_ftello(void) {
85+
FILE *fp = tmpfile();
86+
ftello(fp); // expected-warning {{Stream pointer might be NULL}}
87+
fclose(fp);
88+
}
89+
7490
void check_rewind(void) {
7591
FILE *fp = tmpfile();
7692
rewind(fp); // expected-warning {{Stream pointer might be NULL}}
@@ -129,6 +145,18 @@ void f_dopen(int fd) {
129145
fclose(F);
130146
}
131147

148+
void f_vfprintf(int fd, va_list args) {
149+
FILE *F = fdopen(fd, "r");
150+
vfprintf(F, "%d", args); // expected-warning {{Stream pointer might be NULL}}
151+
fclose(F);
152+
}
153+
154+
void f_vfscanf(int fd, va_list args) {
155+
FILE *F = fdopen(fd, "r");
156+
vfscanf(F, "%u", args); // expected-warning {{Stream pointer might be NULL}}
157+
fclose(F);
158+
}
159+
132160
void f_seek(void) {
133161
FILE *p = fopen("foo", "r");
134162
if (!p)
@@ -138,6 +166,15 @@ void f_seek(void) {
138166
fclose(p);
139167
}
140168

169+
void f_seeko(void) {
170+
FILE *p = fopen("foo", "r");
171+
if (!p)
172+
return;
173+
fseeko(p, 1, SEEK_SET); // no-warning
174+
fseeko(p, 1, 3); // expected-warning {{The whence argument to fseek() should be SEEK_SET, SEEK_END, or SEEK_CUR}}
175+
fclose(p);
176+
}
177+
141178
void f_double_close(void) {
142179
FILE *p = fopen("foo", "r");
143180
if (!p)

0 commit comments

Comments
 (0)