Skip to content

Commit 6b55204

Browse files
Move getdelim/getline preconditions to UnixAPIChecker
1 parent 976cf7c commit 6b55204

File tree

3 files changed

+145
-139
lines changed

3 files changed

+145
-139
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 15 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
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"
2221
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
2322
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
2423
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
@@ -235,8 +234,6 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
235234
BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};
236235
BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
237236
/*SuppressOnSink =*/true};
238-
BugType BT_ArgumentNull{this, "NULL pointer", categories::UnixAPI};
239-
BugType BT_IllegalSize{this, "Invalid buffer size", categories::MemoryError};
240237

241238
public:
242239
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
@@ -349,10 +346,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
349346
{&StreamChecker::preWrite,
350347
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
351348
{{{"getdelim"}, 4},
352-
{std::bind(&StreamChecker::preGetdelim, _1, _2, _3, _4),
349+
{&StreamChecker::preRead,
353350
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
354351
{{{"getline"}, 3},
355-
{std::bind(&StreamChecker::preGetdelim, _1, _2, _3, _4),
352+
{&StreamChecker::preRead,
356353
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
357354
{{{"fseek"}, 3},
358355
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
@@ -448,9 +445,6 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
448445
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
449446
CheckerContext &C) const;
450447

451-
void preGetdelim(const FnDescription *Desc, const CallEvent &Call,
452-
CheckerContext &C) const;
453-
454448
void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
455449
CheckerContext &C) const;
456450

@@ -502,12 +496,6 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
502496
CheckerContext &C,
503497
ProgramStateRef State) const;
504498

505-
ProgramStateRef
506-
ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr, CheckerContext &C,
507-
ProgramStateRef State, const StringRef PtrDescr,
508-
std::optional<std::reference_wrapper<const BugType>> BT =
509-
std::nullopt) const;
510-
511499
/// Check that the stream is the opened state.
512500
/// If the stream is known to be not opened an error is generated
513501
/// and nullptr returned, otherwise the original state is returned.
@@ -531,10 +519,6 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
531519
ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
532520
ProgramStateRef State) const;
533521

534-
ProgramStateRef ensureGetdelimBufferAndSizeCorrect(
535-
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
536-
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const;
537-
538522
/// Generate warning about stream in EOF state.
539523
/// There will be always a state transition into the passed State,
540524
/// by the new non-fatal error node or (if failed) a normal transition,
@@ -1195,110 +1179,6 @@ void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
11951179
C.addTransition(StateFailed);
11961180
}
11971181

1198-
ProgramStateRef StreamChecker::ensureGetdelimBufferAndSizeCorrect(
1199-
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
1200-
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
1201-
static constexpr llvm::StringLiteral SizeGreaterThanBufferSize =
1202-
"The buffer from the first argument is smaller than the size "
1203-
"specified by the second parameter";
1204-
static constexpr llvm::StringLiteral SizeUndef =
1205-
"The buffer from the first argument is not NULL, but the size specified "
1206-
"by the second parameter is undefined.";
1207-
1208-
auto EmitBugReport = [this, &C, SizePtrExpr, LinePtrPtrExpr](
1209-
ProgramStateRef BugState, StringRef ErrMsg) {
1210-
if (ExplodedNode *N = C.generateErrorNode(BugState)) {
1211-
auto R =
1212-
std::make_unique<PathSensitiveBugReport>(BT_IllegalSize, ErrMsg, N);
1213-
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1214-
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1215-
C.emitReport(std::move(R));
1216-
}
1217-
};
1218-
1219-
// We have a pointer to a pointer to the buffer, and a pointer to the size.
1220-
// We want what they point at.
1221-
auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
1222-
auto NSVal = getPointeeVal(SizePtrSVal, State);
1223-
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
1224-
return nullptr;
1225-
1226-
assert(LinePtrPtrExpr && SizePtrExpr);
1227-
1228-
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
1229-
if (LinePtrNotNull && !LinePtrNull) {
1230-
// If `*lineptr` is not null, but `*n` is undefined, there is UB.
1231-
if (NSVal->isUndef()) {
1232-
EmitBugReport(LinePtrNotNull, SizeUndef);
1233-
return nullptr;
1234-
}
1235-
1236-
// If it is defined, and known, its size must be less than or equal to
1237-
// the buffer size.
1238-
auto NDefSVal = NSVal->getAs<DefinedSVal>();
1239-
auto &SVB = C.getSValBuilder();
1240-
auto LineBufSize =
1241-
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
1242-
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
1243-
*NDefSVal, SVB.getConditionType())
1244-
.getAs<DefinedOrUnknownSVal>();
1245-
if (!LineBufSizeGtN)
1246-
return LinePtrNotNull;
1247-
if (auto LineBufSizeOk = LinePtrNotNull->assume(*LineBufSizeGtN, true))
1248-
return LineBufSizeOk;
1249-
1250-
EmitBugReport(LinePtrNotNull, SizeGreaterThanBufferSize);
1251-
return nullptr;
1252-
}
1253-
return State;
1254-
}
1255-
1256-
void StreamChecker::preGetdelim(const FnDescription *Desc,
1257-
const CallEvent &Call,
1258-
CheckerContext &C) const {
1259-
ProgramStateRef State = C.getState();
1260-
SVal StreamVal = getStreamArg(Desc, Call);
1261-
1262-
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1263-
State);
1264-
if (!State)
1265-
return;
1266-
State = ensureStreamOpened(StreamVal, C, State);
1267-
if (!State)
1268-
return;
1269-
State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
1270-
if (!State)
1271-
return;
1272-
1273-
// The parameter `n` must not be NULL.
1274-
SVal SizePtrSval = Call.getArgSVal(1);
1275-
State = ensurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");
1276-
if (!State)
1277-
return;
1278-
1279-
// The parameter `lineptr` must not be NULL.
1280-
SVal LinePtrPtrSVal = Call.getArgSVal(0);
1281-
State =
1282-
ensurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");
1283-
if (!State)
1284-
return;
1285-
1286-
State = ensureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval,
1287-
Call.getArgExpr(0),
1288-
Call.getArgExpr(1), C, State);
1289-
if (!State)
1290-
return;
1291-
1292-
SymbolRef Sym = StreamVal.getAsSymbol();
1293-
if (Sym && State->get<StreamMap>(Sym)) {
1294-
const StreamState *SS = State->get<StreamMap>(Sym);
1295-
if (SS->ErrorState & ErrorFEof)
1296-
reportFEofWarning(Sym, C, State);
1297-
}
1298-
1299-
C.addTransition(State);
1300-
}
1301-
13021182
void StreamChecker::evalGetdelim(const FnDescription *Desc,
13031183
const CallEvent &Call,
13041184
CheckerContext &C) const {
@@ -1673,31 +1553,27 @@ ProgramStateRef
16731553
StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
16741554
CheckerContext &C,
16751555
ProgramStateRef State) const {
1676-
return ensurePtrNotNull(StreamVal, StreamE, C, State, "Stream", BT_FileNull);
1677-
}
1678-
1679-
ProgramStateRef StreamChecker::ensurePtrNotNull(
1680-
SVal PtrVal, const Expr *PtrExpr, CheckerContext &C, ProgramStateRef State,
1681-
const StringRef PtrDescr,
1682-
std::optional<std::reference_wrapper<const BugType>> BT) const {
1683-
const auto Ptr = PtrVal.getAs<DefinedSVal>();
1684-
if (!Ptr)
1556+
auto Stream = StreamVal.getAs<DefinedSVal>();
1557+
if (!Stream)
16851558
return State;
16861559

1687-
const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
1688-
if (!PtrNotNull && PtrNull) {
1689-
if (ExplodedNode *N = C.generateErrorNode(PtrNull)) {
1560+
ConstraintManager &CM = C.getConstraintManager();
1561+
1562+
ProgramStateRef StateNotNull, StateNull;
1563+
std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream);
1564+
1565+
if (!StateNotNull && StateNull) {
1566+
if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
16901567
auto R = std::make_unique<PathSensitiveBugReport>(
1691-
BT.value_or(std::cref(BT_ArgumentNull)),
1692-
(PtrDescr + " pointer might be NULL.").str(), N);
1693-
if (PtrExpr)
1694-
bugreporter::trackExpressionValue(N, PtrExpr, *R);
1568+
BT_FileNull, "Stream pointer might be NULL.", N);
1569+
if (StreamE)
1570+
bugreporter::trackExpressionValue(N, StreamE, *R);
16951571
C.emitReport(std::move(R));
16961572
}
16971573
return nullptr;
16981574
}
16991575

1700-
return PtrNotNull;
1576+
return StateNotNull;
17011577
}
17021578

17031579
ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,

clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
2121
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2222
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
23+
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
2324
#include "llvm/ADT/STLExtras.h"
2425
#include "llvm/ADT/SmallString.h"
2526
#include "llvm/ADT/StringExtras.h"
@@ -44,10 +45,23 @@ namespace {
4445
class UnixAPIMisuseChecker
4546
: public Checker<check::PreCall, check::ASTDecl<TranslationUnitDecl>> {
4647
const BugType BT_open{this, "Improper use of 'open'", categories::UnixAPI};
48+
const BugType BT_getline{this, "Improper use of getdelim",
49+
categories::UnixAPI};
4750
const BugType BT_pthreadOnce{this, "Improper use of 'pthread_once'",
4851
categories::UnixAPI};
52+
const BugType BT_ArgumentNull{this, "NULL pointer", categories::UnixAPI};
4953
mutable std::optional<uint64_t> Val_O_CREAT;
5054

55+
ProgramStateRef
56+
EnsurePtrNotNull(SVal PtrVal, const Expr *PtrExpr, CheckerContext &C,
57+
ProgramStateRef State, const StringRef PtrDescr,
58+
std::optional<std::reference_wrapper<const BugType>> BT =
59+
std::nullopt) const;
60+
61+
ProgramStateRef EnsureGetdelimBufferAndSizeCorrect(
62+
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
63+
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const;
64+
5165
public:
5266
void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &Mgr,
5367
BugReporter &BR) const;
@@ -56,6 +70,7 @@ class UnixAPIMisuseChecker
5670

5771
void CheckOpen(CheckerContext &C, const CallEvent &Call) const;
5872
void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const;
73+
void CheckGetDelim(CheckerContext &C, const CallEvent &Call) const;
5974
void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const;
6075

6176
void CheckOpenVariant(CheckerContext &C, const CallEvent &Call,
@@ -95,6 +110,30 @@ class UnixAPIPortabilityChecker : public Checker< check::PreStmt<CallExpr> > {
95110

96111
} // end anonymous namespace
97112

113+
ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull(
114+
SVal PtrVal, const Expr *PtrExpr, CheckerContext &C, ProgramStateRef State,
115+
const StringRef PtrDescr,
116+
std::optional<std::reference_wrapper<const BugType>> BT) const {
117+
const auto Ptr = PtrVal.getAs<DefinedSVal>();
118+
if (!Ptr)
119+
return State;
120+
121+
const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
122+
if (!PtrNotNull && PtrNull) {
123+
if (ExplodedNode *N = C.generateErrorNode(PtrNull)) {
124+
auto R = std::make_unique<PathSensitiveBugReport>(
125+
BT.value_or(std::cref(BT_ArgumentNull)),
126+
(PtrDescr + " pointer might be NULL.").str(), N);
127+
if (PtrExpr)
128+
bugreporter::trackExpressionValue(N, PtrExpr, *R);
129+
C.emitReport(std::move(R));
130+
}
131+
return nullptr;
132+
}
133+
134+
return PtrNotNull;
135+
}
136+
98137
void UnixAPIMisuseChecker::checkASTDecl(const TranslationUnitDecl *TU,
99138
AnalysisManager &Mgr,
100139
BugReporter &) const {
@@ -137,6 +176,9 @@ void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call,
137176

138177
else if (FName == "pthread_once")
139178
CheckPthreadOnce(C, Call);
179+
180+
else if (is_contained({"getdelim", "getline"}, FName))
181+
CheckGetDelim(C, Call);
140182
}
141183
void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C,
142184
ProgramStateRef State,
@@ -266,6 +308,94 @@ void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C,
266308
}
267309
}
268310

311+
//===----------------------------------------------------------------------===//
312+
// getdelim and getline
313+
//===----------------------------------------------------------------------===//
314+
315+
ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
316+
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
317+
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
318+
static constexpr llvm::StringLiteral SizeGreaterThanBufferSize =
319+
"The buffer from the first argument is smaller than the size "
320+
"specified by the second parameter";
321+
static constexpr llvm::StringLiteral SizeUndef =
322+
"The buffer from the first argument is not NULL, but the size specified "
323+
"by the second parameter is undefined.";
324+
325+
auto EmitBugReport = [this, &C, SizePtrExpr, LinePtrPtrExpr](
326+
ProgramStateRef BugState, StringRef ErrMsg) {
327+
if (ExplodedNode *N = C.generateErrorNode(BugState)) {
328+
auto R = std::make_unique<PathSensitiveBugReport>(BT_getline, ErrMsg, N);
329+
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
330+
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
331+
C.emitReport(std::move(R));
332+
}
333+
};
334+
335+
// We have a pointer to a pointer to the buffer, and a pointer to the size.
336+
// We want what they point at.
337+
auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
338+
auto NSVal = getPointeeVal(SizePtrSVal, State);
339+
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
340+
return nullptr;
341+
342+
assert(LinePtrPtrExpr && SizePtrExpr);
343+
344+
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
345+
if (LinePtrNotNull && !LinePtrNull) {
346+
// If `*lineptr` is not null, but `*n` is undefined, there is UB.
347+
if (NSVal->isUndef()) {
348+
EmitBugReport(LinePtrNotNull, SizeUndef);
349+
return nullptr;
350+
}
351+
352+
// If it is defined, and known, its size must be less than or equal to
353+
// the buffer size.
354+
auto NDefSVal = NSVal->getAs<DefinedSVal>();
355+
auto &SVB = C.getSValBuilder();
356+
auto LineBufSize =
357+
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
358+
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
359+
*NDefSVal, SVB.getConditionType())
360+
.getAs<DefinedOrUnknownSVal>();
361+
if (!LineBufSizeGtN)
362+
return LinePtrNotNull;
363+
if (auto LineBufSizeOk = LinePtrNotNull->assume(*LineBufSizeGtN, true))
364+
return LineBufSizeOk;
365+
366+
EmitBugReport(LinePtrNotNull, SizeGreaterThanBufferSize);
367+
return nullptr;
368+
}
369+
return State;
370+
}
371+
372+
void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C,
373+
const CallEvent &Call) const {
374+
ProgramStateRef State = C.getState();
375+
376+
// The parameter `n` must not be NULL.
377+
SVal SizePtrSval = Call.getArgSVal(1);
378+
State = EnsurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");
379+
if (!State)
380+
return;
381+
382+
// The parameter `lineptr` must not be NULL.
383+
SVal LinePtrPtrSVal = Call.getArgSVal(0);
384+
State =
385+
EnsurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");
386+
if (!State)
387+
return;
388+
389+
State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval,
390+
Call.getArgExpr(0),
391+
Call.getArgExpr(1), C, State);
392+
if (!State)
393+
return;
394+
395+
C.addTransition(State);
396+
}
397+
398+
269399
//===----------------------------------------------------------------------===//
270400
// pthread_once
271401
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)