Skip to content

Commit efd1411

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 d0b1fec commit efd1411

File tree

5 files changed

+496
-2
lines changed

5 files changed

+496
-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>
@@ -312,6 +313,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
312313
BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};
313314
BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
314315
/*SuppressOnSink =*/true};
316+
BugType BT_SizeNull{this, "NULL size pointer", "Stream handling error"};
317+
BugType BT_SizeNotZero{this, "NULL line pointer and size not zero",
318+
"Stream handling error"};
315319

316320
public:
317321
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
@@ -364,10 +368,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
364368
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
365369
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
366370
{{{"getdelim"}, 4},
367-
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
371+
{std::bind(&StreamChecker::preGetdelim, _1, _2, _3, _4),
368372
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},
369373
{{{"getline"}, 3},
370-
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
374+
{std::bind(&StreamChecker::preGetdelim, _1, _2, _3, _4),
371375
std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},
372376
{{{"fseek"}, 3},
373377
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
@@ -452,6 +456,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
452456
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
453457
CheckerContext &C) const;
454458

459+
void preGetdelim(const FnDescription *Desc, const CallEvent &Call,
460+
CheckerContext &C) const;
461+
455462
void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,
456463
CheckerContext &C) const;
457464

@@ -526,6 +533,14 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
526533
ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
527534
ProgramStateRef State) const;
528535

536+
ProgramStateRef ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr,
537+
CheckerContext &C, ProgramStateRef State,
538+
const StringRef PtrDescr) const;
539+
ProgramStateRef
540+
ensureSizeZeroIfLineNull(SVal LinePtrPtrSVal, SVal SizePtrSVal,
541+
const Expr *LinePtrPtrExpr, const Expr *SizePtrExpr,
542+
CheckerContext &C, ProgramStateRef State) const;
543+
529544
/// Generate warning about stream in EOF state.
530545
/// There will be always a state transition into the passed State,
531546
/// by the new non-fatal error node or (if failed) a normal transition,
@@ -1091,6 +1106,123 @@ void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
10911106
C.addTransition(StateFailed);
10921107
}
10931108

1109+
ProgramStateRef
1110+
StreamChecker::ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr,
1111+
CheckerContext &C, ProgramStateRef State,
1112+
const StringRef PtrDescr) const {
1113+
const auto Ptr = PtrVal.getAs<DefinedSVal>();
1114+
if (!Ptr)
1115+
return nullptr;
1116+
1117+
assert(PtrExpr && "Expected an argument");
1118+
1119+
const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
1120+
if (!PtrNotNull && PtrNull) {
1121+
if (ExplodedNode *N = C.generateErrorNode(PtrNull)) {
1122+
SmallString<256> buf;
1123+
llvm::raw_svector_ostream os(buf);
1124+
os << PtrDescr << " pointer might be NULL.";
1125+
1126+
auto R = std::make_unique<PathSensitiveBugReport>(BT_SizeNull, buf, N);
1127+
bugreporter::trackExpressionValue(N, PtrExpr, *R);
1128+
C.emitReport(std::move(R));
1129+
}
1130+
return nullptr;
1131+
}
1132+
1133+
return PtrNotNull;
1134+
}
1135+
1136+
ProgramStateRef StreamChecker::ensureSizeZeroIfLineNull(
1137+
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
1138+
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
1139+
static constexpr char SizeNotZeroMsg[] =
1140+
"Line pointer might be null while n value is not zero";
1141+
1142+
// We have a pointer to a pointer to the buffer, and a pointer to the size.
1143+
// We want what they point at.
1144+
auto LinePtrSVal = getPointeeDefVal(LinePtrPtrSVal, State);
1145+
auto NSVal = getPointeeDefVal(SizePtrSVal, State);
1146+
if (!LinePtrSVal || !NSVal)
1147+
return nullptr;
1148+
1149+
assert(LinePtrPtrExpr &&
1150+
"Expected an argument with a pointer to a pointer to the buffer.");
1151+
assert(SizePtrExpr &&
1152+
"Expected an argument with a pointer to the buffer size.");
1153+
1154+
// If the line pointer is null, and n is > 0, there is UB.
1155+
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
1156+
if (LinePtrNull && !LinePtrNotNull) {
1157+
const auto [NIsNotZero, NIsZero] = LinePtrNull->assume(*NSVal);
1158+
if (NIsNotZero && !NIsZero) {
1159+
if (ExplodedNode *N = C.generateErrorNode(NIsNotZero)) {
1160+
auto R = std::make_unique<PathSensitiveBugReport>(BT_SizeNotZero,
1161+
SizeNotZeroMsg, N);
1162+
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1163+
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1164+
C.emitReport(std::move(R));
1165+
}
1166+
return nullptr;
1167+
}
1168+
return NIsZero;
1169+
}
1170+
return LinePtrNotNull;
1171+
}
1172+
1173+
void StreamChecker::preGetdelim(const FnDescription *Desc,
1174+
const CallEvent &Call,
1175+
CheckerContext &C) const {
1176+
ProgramStateRef State = C.getState();
1177+
SVal StreamVal = getStreamArg(Desc, Call);
1178+
1179+
auto AddTransitionOnReturn = llvm::make_scope_exit([&] {
1180+
if (State != nullptr) {
1181+
C.addTransition(State);
1182+
}
1183+
});
1184+
1185+
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1186+
State);
1187+
if (!State)
1188+
return;
1189+
State = ensureStreamOpened(StreamVal, C, State);
1190+
if (!State)
1191+
return;
1192+
State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
1193+
if (!State)
1194+
return;
1195+
1196+
// n must not be NULL
1197+
SVal SizePtrSval = Call.getArgSVal(1);
1198+
State = ensurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");
1199+
if (!State)
1200+
return;
1201+
1202+
// lineptr must not be NULL
1203+
SVal LinePtrPtrSVal = Call.getArgSVal(0);
1204+
State =
1205+
ensurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");
1206+
if (!State)
1207+
return;
1208+
1209+
// If lineptr points to a NULL pointer, *n must be 0
1210+
State =
1211+
ensureSizeZeroIfLineNull(LinePtrPtrSVal, SizePtrSval, Call.getArgExpr(0),
1212+
Call.getArgExpr(1), C, State);
1213+
if (!State)
1214+
return;
1215+
1216+
SymbolRef Sym = StreamVal.getAsSymbol();
1217+
if (Sym && State->get<StreamMap>(Sym)) {
1218+
const StreamState *SS = State->get<StreamMap>(Sym);
1219+
if (SS->ErrorState & ErrorFEof)
1220+
reportFEofWarning(Sym, C, State);
1221+
} else {
1222+
C.addTransition(State);
1223+
}
1224+
}
1225+
10941226
void StreamChecker::evalGetdelim(const FnDescription *Desc,
10951227
const CallEvent &Call,
10961228
CheckerContext &C) const {
@@ -1116,6 +1248,20 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
11161248
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
11171249
StateNotFailed =
11181250
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1251+
// The buffer size *n must be enough to hold the whole line, and
1252+
// greater than the return value, since it has to account for \0
1253+
auto SizePtrSval = Call.getArgSVal(1);
1254+
auto NVal = getPointeeDefVal(SizePtrSval, State);
1255+
if (NVal) {
1256+
StateNotFailed = StateNotFailed->assume(
1257+
E.SVB
1258+
.evalBinOp(StateNotFailed, BinaryOperator::Opcode::BO_GT, *NVal,
1259+
RetVal, E.SVB.getConditionType())
1260+
.castAs<DefinedOrUnknownSVal>(),
1261+
true);
1262+
StateNotFailed =
1263+
StateNotFailed->BindExpr(E.CE, C.getLocationContext(), RetVal);
1264+
}
11191265
if (!StateNotFailed)
11201266
return;
11211267
C.addTransition(StateNotFailed);
@@ -1129,6 +1275,11 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
11291275
E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
11301276
StateFailed = E.setStreamState(
11311277
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
1278+
// On failure, the content of the buffer is undefined
1279+
if (auto NewLinePtr = getPointeeDefVal(Call.getArgSVal(0), State)) {
1280+
StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),
1281+
C.getLocationContext());
1282+
}
11321283
if (E.isStreamEof())
11331284
C.addTransition(StateFailed, constructSetEofNoteTag(C, E.StreamSym));
11341285
else

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ typedef __typeof(sizeof(int)) size_t;
99
void *malloc(size_t);
1010
void *calloc(size_t, size_t);
1111
void free(void *);
12+
void *alloca(size_t);
1213

1314

1415
#if __OBJC__

0 commit comments

Comments
 (0)