Skip to content

Commit 9db5a4a

Browse files
Model getline/getdelim according to posix 2018
1 parent 25e7bb3 commit 9db5a4a

File tree

2 files changed

+109
-34
lines changed

2 files changed

+109
-34
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
1919
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2020
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
21+
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
2122
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
2223
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
2324
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
@@ -235,8 +236,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
235236
BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
236237
/*SuppressOnSink =*/true};
237238
BugType BT_SizeNull{this, "NULL size pointer", "Stream handling error"};
238-
BugType BT_SizeNotZero{this, "NULL line pointer and size not zero",
239-
"Stream handling error"};
239+
BugType BT_SizeGreaterThanBufferSize{
240+
this, "Size greater than the allocated buffer size",
241+
categories::MemoryError};
240242

241243
public:
242244
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
@@ -1219,8 +1221,11 @@ StreamChecker::ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr,
12191221
ProgramStateRef StreamChecker::ensureGetdelimBufferAndSizeCorrect(
12201222
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
12211223
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
1222-
static constexpr char SizeNotZeroMsg[] =
1223-
"Line pointer might be null while n value is not zero";
1224+
// If the argument `*n` is non-zero, `*lineptr` must point to an object of
1225+
// size at least `*n` bytes, or a `NULL` pointer.
1226+
static constexpr char SizeGreaterThanBufferSize[] =
1227+
"The buffer from the first argument is smaller than the size "
1228+
"specified by the second parameter";
12241229

12251230
// We have a pointer to a pointer to the buffer, and a pointer to the size.
12261231
// We want what they point at.
@@ -1231,23 +1236,31 @@ ProgramStateRef StreamChecker::ensureGetdelimBufferAndSizeCorrect(
12311236

12321237
assert(LinePtrPtrExpr && SizePtrExpr);
12331238

1234-
// If the line pointer is null, and n is > 0, there is UB.
12351239
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
1236-
if (LinePtrNull && !LinePtrNotNull) {
1237-
const auto [NIsNotZero, NIsZero] = LinePtrNull->assume(*NSVal);
1238-
if (NIsNotZero && !NIsZero) {
1239-
if (ExplodedNode *N = C.generateErrorNode(NIsNotZero)) {
1240-
auto R = std::make_unique<PathSensitiveBugReport>(BT_SizeNotZero,
1241-
SizeNotZeroMsg, N);
1242-
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1243-
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1244-
C.emitReport(std::move(R));
1245-
}
1246-
return nullptr;
1240+
if (LinePtrNotNull && !LinePtrNull) {
1241+
auto &SVB = C.getSValBuilder();
1242+
auto LineBufSize =
1243+
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
1244+
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
1245+
*NSVal, SVB.getConditionType())
1246+
.getAs<DefinedOrUnknownSVal>();
1247+
if (!LineBufSizeGtN) {
1248+
return LinePtrNotNull;
1249+
}
1250+
if (auto LineBufSizeOk = LinePtrNotNull->assume(*LineBufSizeGtN, true)) {
1251+
return LineBufSizeOk;
12471252
}
1248-
return NIsZero;
1253+
1254+
if (ExplodedNode *N = C.generateErrorNode(LinePtrNotNull)) {
1255+
auto R = std::make_unique<PathSensitiveBugReport>(
1256+
BT_SizeGreaterThanBufferSize, SizeGreaterThanBufferSize, N);
1257+
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1258+
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1259+
C.emitReport(std::move(R));
1260+
}
1261+
return nullptr;
12491262
}
1250-
return LinePtrNotNull;
1263+
return State;
12511264
}
12521265

12531266
void StreamChecker::preGetdelim(const FnDescription *Desc,

clang/test/Analysis/getline-stream.c

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

33
#include "Inputs/system-header-simulator.h"
44
#include "Inputs/system-header-simulator-for-malloc.h"
@@ -35,24 +35,26 @@ void test_getline_null_size() {
3535
fclose(F1);
3636
}
3737

38-
void test_getline_null_buffer_bad_size() {
38+
void test_getline_null_buffer_size_gt0() {
3939
FILE *F1 = tmpfile();
4040
if (!F1)
4141
return;
4242
char *buffer = NULL;
4343
size_t n = 8;
44-
getline(&buffer, &n, F1); // expected-warning {{Line pointer might be null while n value is not zero}}
44+
getline(&buffer, &n, F1); // ok since posix 2018
45+
free(buffer);
4546
fclose(F1);
4647
}
4748

48-
void test_getline_null_buffer_bad_size_2(size_t n) {
49+
void test_getline_null_buffer_size_gt0_2(size_t n) {
4950
FILE *F1 = tmpfile();
5051
if (!F1)
5152
return;
5253
char *buffer = NULL;
5354
if (n > 0) {
54-
getline(&buffer, &n, F1); // expected-warning {{Line pointer might be null while n value is not zero}}
55+
getline(&buffer, &n, F1); // ok since posix 2018
5556
}
57+
free(buffer);
5658
fclose(F1);
5759
}
5860

@@ -67,6 +69,58 @@ void test_getline_null_buffer_unknown_size(size_t n) {
6769
free(buffer);
6870
}
6971

72+
void test_getline_null_buffer_undef_size() {
73+
FILE *F1 = tmpfile();
74+
if (!F1)
75+
return;
76+
77+
char *buffer = NULL;
78+
size_t n;
79+
80+
getline(&buffer, &n, F1); // ok since posix 2018
81+
fclose(F1);
82+
free(buffer);
83+
}
84+
85+
void test_getline_buffer_size_0() {
86+
FILE *F1 = tmpfile();
87+
if (!F1)
88+
return;
89+
90+
char *buffer = malloc(10);
91+
size_t n = 0;
92+
if (buffer != NULL)
93+
getline(&buffer, &n, F1); // ok, the buffer is enough for 0 character
94+
fclose(F1);
95+
free(buffer);
96+
}
97+
98+
void test_getline_buffer_bad_size() {
99+
FILE *F1 = tmpfile();
100+
if (!F1)
101+
return;
102+
103+
char *buffer = malloc(10);
104+
size_t n = 100;
105+
if (buffer != NULL)
106+
getline(&buffer, &n, F1); // expected-warning {{The buffer from the first argument is smaller than the size specified by the second parameter}}
107+
fclose(F1);
108+
free(buffer);
109+
}
110+
111+
void test_getline_buffer_smaller_size() {
112+
FILE *F1 = tmpfile();
113+
if (!F1)
114+
return;
115+
116+
char *buffer = malloc(100);
117+
size_t n = 10;
118+
if (buffer != NULL)
119+
getline(&buffer, &n, F1); // ok, there is enough space for 10 characters
120+
fclose(F1);
121+
free(buffer);
122+
}
123+
70124
void test_getline_null_buffer() {
71125
FILE *F1 = tmpfile();
72126
if (!F1)
@@ -101,24 +155,26 @@ void test_getdelim_null_size() {
101155
fclose(F1);
102156
}
103157

104-
void test_getdelim_null_buffer_bad_size() {
158+
void test_getdelim_null_buffer_size_gt0() {
105159
FILE *F1 = tmpfile();
106160
if (!F1)
107161
return;
108162
char *buffer = NULL;
109163
size_t n = 8;
110-
getdelim(&buffer, &n, ';', F1); // expected-warning {{Line pointer might be null while n value is not zero}}
164+
getdelim(&buffer, &n, ';', F1); // ok since posix 2018
165+
free(buffer);
111166
fclose(F1);
112167
}
113168

114-
void test_getdelim_null_buffer_bad_size_2(size_t n) {
169+
void test_getdelim_null_buffer_size_gt0_2(size_t n) {
115170
FILE *F1 = tmpfile();
116171
if (!F1)
117172
return;
118173
char *buffer = NULL;
119174
if (n > 0) {
120-
getdelim(&buffer, &n, ' ', F1); // expected-warning {{Line pointer might be null while n value is not zero}}
175+
getdelim(&buffer, &n, ' ', F1); // ok since posix 2018
121176
}
177+
free(buffer);
122178
fclose(F1);
123179
}
124180

@@ -289,18 +345,21 @@ void test_getline_not_null(char **buffer, size_t *size) {
289345
}
290346
}
291347

292-
void test_getline_size_0(size_t size) {
348+
void test_getline_size_constraint(size_t size) {
293349
FILE *file = fopen("file.txt", "r");
294350
if (file == NULL) {
295351
return;
296352
}
297353

298354
size_t old_size = size;
299-
char *buffer = NULL;
300-
ssize_t r = getline(&buffer, &size, file);
301-
if (r >= 0) {
302-
// Since buffer is NULL, old_size should be 0. Otherwise, there would be UB.
303-
clang_analyzer_eval(old_size == 0); // expected-warning{{TRUE}}
355+
char *buffer = malloc(10);
356+
if (buffer != NULL) {
357+
ssize_t r = getline(&buffer, &size, file);
358+
if (r >= 0) {
359+
// Since buffer has a size of 10, old_size must be less than or equal to 10.
360+
// Otherwise, there would be UB.
361+
clang_analyzer_eval(old_size <= 10); // expected-warning{{TRUE}}
362+
}
304363
}
305364
fclose(file);
306365
free(buffer);
@@ -334,7 +393,9 @@ void test_getline_negative_buffer() {
334393

335394
char *buffer = NULL;
336395
size_t n = -1;
337-
getline(&buffer, &n, file); // expected-warning {{Line pointer might be null while n value is not zero}}
396+
getline(&buffer, &n, file); // ok since posix 2018
397+
free(buffer);
398+
fclose(file);
338399
}
339400

340401
void test_getline_negative_buffer_2(char *buffer) {
@@ -348,5 +409,6 @@ void test_getline_negative_buffer_2(char *buffer) {
348409
if (ret > 0) {
349410
clang_analyzer_eval(ret < n); // expected-warning {{TRUE}}
350411
}
412+
free(buffer);
351413
fclose(file);
352414
}

0 commit comments

Comments
 (0)