21
21
#include " clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
22
22
#include " clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
23
23
#include " clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
24
+ #include " llvm/ADT/ScopeExit.h"
24
25
#include " llvm/ADT/Sequence.h"
25
26
#include < functional>
26
27
#include < optional>
@@ -234,6 +235,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
234
235
BugType BT_StreamEof{this , " Stream already in EOF" , " Stream handling error" };
235
236
BugType BT_ResourceLeak{this , " Resource leak" , " Stream handling error" ,
236
237
/* 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" };
237
241
238
242
public:
239
243
void checkPreCall (const CallEvent &Call, CheckerContext &C) const ;
@@ -346,10 +350,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
346
350
{std::bind (&StreamChecker::preReadWrite, _1, _2, _3, _4, false ),
347
351
std::bind (&StreamChecker::evalUngetc, _1, _2, _3, _4), 1 }},
348
352
{{{" getdelim" }, 4 },
349
- {std::bind (&StreamChecker::preReadWrite , _1, _2, _3, _4, true ),
353
+ {std::bind (&StreamChecker::preGetdelim , _1, _2, _3, _4),
350
354
std::bind (&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3 }},
351
355
{{{" getline" }, 3 },
352
- {std::bind (&StreamChecker::preReadWrite , _1, _2, _3, _4, true ),
356
+ {std::bind (&StreamChecker::preGetdelim , _1, _2, _3, _4),
353
357
std::bind (&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2 }},
354
358
{{{" fseek" }, 3 },
355
359
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0 }},
@@ -436,6 +440,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
436
440
void evalUngetc (const FnDescription *Desc, const CallEvent &Call,
437
441
CheckerContext &C) const ;
438
442
443
+ void preGetdelim (const FnDescription *Desc, const CallEvent &Call,
444
+ CheckerContext &C) const ;
445
+
439
446
void evalGetdelim (const FnDescription *Desc, const CallEvent &Call,
440
447
CheckerContext &C) const ;
441
448
@@ -510,6 +517,14 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
510
517
ProgramStateRef ensureFseekWhenceCorrect (SVal WhenceVal, CheckerContext &C,
511
518
ProgramStateRef State) const ;
512
519
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
+
513
528
// / Generate warning about stream in EOF state.
514
529
// / There will be always a state transition into the passed State,
515
530
// / 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,
1158
1173
C.addTransition (StateFailed);
1159
1174
}
1160
1175
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
+
1161
1293
void StreamChecker::evalGetdelim (const FnDescription *Desc,
1162
1294
const CallEvent &Call,
1163
1295
CheckerContext &C) const {
@@ -1183,6 +1315,20 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
1183
1315
State->BindExpr (E.CE , C.getLocationContext (), RetVal);
1184
1316
StateNotFailed =
1185
1317
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
+ }
1186
1332
if (!StateNotFailed)
1187
1333
return ;
1188
1334
C.addTransition (StateNotFailed);
@@ -1196,6 +1342,11 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
1196
1342
E.isStreamEof () ? ErrorFEof : ErrorFEof | ErrorFError;
1197
1343
StateFailed = E.setStreamState (
1198
1344
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
+ }
1199
1350
C.addTransition (StateFailed, E.getFailureNoteTag (this , C));
1200
1351
}
1201
1352
0 commit comments