Skip to content

Commit 9975018

Browse files
[clang][analyzer] Model getline/getdelim preconditions
1. lineptr, n and stream can not be NULL 2. if *lineptr is NULL, *n must be 0
1 parent f7d354a commit 9975018

File tree

4 files changed

+495
-2
lines changed

4 files changed

+495
-2
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H
1414
#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H
1515

16+
#include "ProgramState_Fwd.h"
17+
#include "SVals.h"
18+
1619
#include "clang/AST/OperationKinds.h"
1720
#include "clang/AST/Stmt.h"
1821
#include "clang/Basic/OperatorKinds.h"
@@ -110,6 +113,9 @@ class OperatorKind {
110113
OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK,
111114
bool IsBinary);
112115

116+
std::optional<DefinedSVal> getPointeeDefVal(SVal PtrSVal,
117+
ProgramStateRef State);
118+
113119
} // namespace ento
114120

115121
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
2222
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
2323
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
24+
#include "llvm/ADT/ScopeExit.h"
2425
#include "llvm/ADT/Sequence.h"
2526
#include <functional>
2627
#include <optional>
@@ -234,6 +235,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
234235
BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};
235236
BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
236237
/*SuppressOnSink =*/true};
238+
BugType BT_SizeNull{this, "NULL size pointer", "Stream handling error"};
239+
BugType BT_SizeNotZero{this, "NULL line pointer and size not zero",
240+
"Stream handling error"};
237241

238242
public:
239243
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
@@ -346,10 +350,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
346350
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
347351
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
348352
{{{"getdelim"}, 4},
349-
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
353+
{std::bind(&StreamChecker::preGetdelim, _1, _2, _3, _4),
350354
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
351355
{{{"getline"}, 3},
352-
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
356+
{std::bind(&StreamChecker::preGetdelim, _1, _2, _3, _4),
353357
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
354358
{{{"fseek"}, 3},
355359
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
@@ -436,6 +440,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
436440
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
437441
CheckerContext &C) const;
438442

443+
void preGetdelim(const FnDescription *Desc, const CallEvent &Call,
444+
CheckerContext &C) const;
445+
439446
void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
440447
CheckerContext &C) const;
441448

@@ -510,6 +517,14 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
510517
ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
511518
ProgramStateRef State) const;
512519

520+
ProgramStateRef ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr,
521+
CheckerContext &C, ProgramStateRef State,
522+
const StringRef PtrDescr) const;
523+
ProgramStateRef
524+
ensureSizeZeroIfLineNull(SVal LinePtrPtrSVal, SVal SizePtrSVal,
525+
const Expr *LinePtrPtrExpr, const Expr *SizePtrExpr,
526+
CheckerContext &C, ProgramStateRef State) const;
527+
513528
/// Generate warning about stream in EOF state.
514529
/// There will be always a state transition into the passed State,
515530
/// by the new non-fatal error node or (if failed) a normal transition,
@@ -1158,6 +1173,123 @@ void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
11581173
C.addTransition(StateFailed);
11591174
}
11601175

1176+
ProgramStateRef
1177+
StreamChecker::ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr,
1178+
CheckerContext &C, ProgramStateRef State,
1179+
const StringRef PtrDescr) const {
1180+
const auto Ptr = PtrVal.getAs<DefinedSVal>();
1181+
if (!Ptr)
1182+
return nullptr;
1183+
1184+
assert(PtrExpr && "Expected an argument");
1185+
1186+
const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
1187+
if (!PtrNotNull && PtrNull) {
1188+
if (ExplodedNode *N = C.generateErrorNode(PtrNull)) {
1189+
SmallString<256> buf;
1190+
llvm::raw_svector_ostream os(buf);
1191+
os << PtrDescr << " pointer might be NULL.";
1192+
1193+
auto R = std::make_unique<PathSensitiveBugReport>(BT_SizeNull, buf, N);
1194+
bugreporter::trackExpressionValue(N, PtrExpr, *R);
1195+
C.emitReport(std::move(R));
1196+
}
1197+
return nullptr;
1198+
}
1199+
1200+
return PtrNotNull;
1201+
}
1202+
1203+
ProgramStateRef StreamChecker::ensureSizeZeroIfLineNull(
1204+
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
1205+
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
1206+
static constexpr char SizeNotZeroMsg[] =
1207+
"Line pointer might be null while n value is not zero";
1208+
1209+
// We have a pointer to a pointer to the buffer, and a pointer to the size.
1210+
// We want what they point at.
1211+
auto LinePtrSVal = getPointeeDefVal(LinePtrPtrSVal, State);
1212+
auto NSVal = getPointeeDefVal(SizePtrSVal, State);
1213+
if (!LinePtrSVal || !NSVal)
1214+
return nullptr;
1215+
1216+
assert(LinePtrPtrExpr &&
1217+
"Expected an argument with a pointer to a pointer to the buffer.");
1218+
assert(SizePtrExpr &&
1219+
"Expected an argument with a pointer to the buffer size.");
1220+
1221+
// If the line pointer is null, and n is > 0, there is UB.
1222+
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
1223+
if (LinePtrNull && !LinePtrNotNull) {
1224+
const auto [NIsNotZero, NIsZero] = LinePtrNull->assume(*NSVal);
1225+
if (NIsNotZero && !NIsZero) {
1226+
if (ExplodedNode *N = C.generateErrorNode(NIsNotZero)) {
1227+
auto R = std::make_unique<PathSensitiveBugReport>(BT_SizeNotZero,
1228+
SizeNotZeroMsg, N);
1229+
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1230+
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1231+
C.emitReport(std::move(R));
1232+
}
1233+
return nullptr;
1234+
}
1235+
return NIsZero;
1236+
}
1237+
return LinePtrNotNull;
1238+
}
1239+
1240+
void StreamChecker::preGetdelim(const FnDescription *Desc,
1241+
const CallEvent &Call,
1242+
CheckerContext &C) const {
1243+
ProgramStateRef State = C.getState();
1244+
SVal StreamVal = getStreamArg(Desc, Call);
1245+
1246+
auto AddTransitionOnReturn = llvm::make_scope_exit([&] {
1247+
if (State != nullptr) {
1248+
C.addTransition(State);
1249+
}
1250+
});
1251+
1252+
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1253+
State);
1254+
if (!State)
1255+
return;
1256+
State = ensureStreamOpened(StreamVal, C, State);
1257+
if (!State)
1258+
return;
1259+
State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
1260+
if (!State)
1261+
return;
1262+
1263+
// n must not be NULL
1264+
SVal SizePtrSval = Call.getArgSVal(1);
1265+
State = ensurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");
1266+
if (!State)
1267+
return;
1268+
1269+
// lineptr must not be NULL
1270+
SVal LinePtrPtrSVal = Call.getArgSVal(0);
1271+
State =
1272+
ensurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");
1273+
if (!State)
1274+
return;
1275+
1276+
// If lineptr points to a NULL pointer, *n must be 0
1277+
State =
1278+
ensureSizeZeroIfLineNull(LinePtrPtrSVal, SizePtrSval, Call.getArgExpr(0),
1279+
Call.getArgExpr(1), C, State);
1280+
if (!State)
1281+
return;
1282+
1283+
SymbolRef Sym = StreamVal.getAsSymbol();
1284+
if (Sym && State->get<StreamMap>(Sym)) {
1285+
const StreamState *SS = State->get<StreamMap>(Sym);
1286+
if (SS->ErrorState & ErrorFEof)
1287+
reportFEofWarning(Sym, C, State);
1288+
} else {
1289+
C.addTransition(State);
1290+
}
1291+
}
1292+
11611293
void StreamChecker::evalGetdelim(const FnDescription *Desc,
11621294
const CallEvent &Call,
11631295
CheckerContext &C) const {
@@ -1183,6 +1315,20 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
11831315
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
11841316
StateNotFailed =
11851317
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1318+
// The buffer size *n must be enough to hold the whole line, and
1319+
// greater than the return value, since it has to account for \0
1320+
auto SizePtrSval = Call.getArgSVal(1);
1321+
auto NVal = getPointeeDefVal(SizePtrSval, State);
1322+
if (NVal) {
1323+
StateNotFailed = StateNotFailed->assume(
1324+
E.SVB
1325+
.evalBinOp(StateNotFailed, BinaryOperator::Opcode::BO_GT, *NVal,
1326+
RetVal, E.SVB.getConditionType())
1327+
.castAs<DefinedOrUnknownSVal>(),
1328+
true);
1329+
StateNotFailed =
1330+
StateNotFailed->BindExpr(E.CE, C.getLocationContext(), RetVal);
1331+
}
11861332
if (!StateNotFailed)
11871333
return;
11881334
C.addTransition(StateNotFailed);
@@ -1196,6 +1342,11 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
11961342
E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
11971343
StateFailed = E.setStreamState(
11981344
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
1345+
// On failure, the content of the buffer is undefined
1346+
if (auto NewLinePtr = getPointeeDefVal(Call.getArgSVal(0), State)) {
1347+
StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),
1348+
C.getLocationContext());
1349+
}
11991350
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
12001351
}
12011352

clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "clang/AST/Decl.h"
1515
#include "clang/AST/Expr.h"
1616
#include "clang/Lex/Preprocessor.h"
17+
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
1718
#include <optional>
1819

1920
namespace clang {
@@ -182,5 +183,13 @@ OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK,
182183
}
183184
}
184185

186+
std::optional<DefinedSVal> getPointeeDefVal(SVal PtrSVal,
187+
ProgramStateRef State) {
188+
if (const auto *Ptr = PtrSVal.getAsRegion()) {
189+
return State->getSVal(Ptr).getAs<DefinedSVal>();
190+
}
191+
return std::nullopt;
192+
}
193+
185194
} // namespace ento
186195
} // namespace clang

0 commit comments

Comments
 (0)