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>
@@ -312,6 +313,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
312
313
BugType BT_StreamEof{this , " Stream already in EOF" , " Stream handling error" };
313
314
BugType BT_ResourceLeak{this , " Resource leak" , " Stream handling error" ,
314
315
/* 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" };
315
319
316
320
public:
317
321
void checkPreCall (const CallEvent &Call, CheckerContext &C) const ;
@@ -364,10 +368,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
364
368
{std::bind (&StreamChecker::preReadWrite, _1, _2, _3, _4, false ),
365
369
std::bind (&StreamChecker::evalUngetc, _1, _2, _3, _4), 1 }},
366
370
{{{" getdelim" }, 4 },
367
- {std::bind (&StreamChecker::preReadWrite , _1, _2, _3, _4, true ),
371
+ {std::bind (&StreamChecker::preGetdelim , _1, _2, _3, _4),
368
372
std::bind (&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3 }},
369
373
{{{" getline" }, 3 },
370
- {std::bind (&StreamChecker::preReadWrite , _1, _2, _3, _4, true ),
374
+ {std::bind (&StreamChecker::preGetdelim , _1, _2, _3, _4),
371
375
std::bind (&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2 }},
372
376
{{{" fseek" }, 3 },
373
377
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0 }},
@@ -452,6 +456,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
452
456
void evalUngetc (const FnDescription *Desc, const CallEvent &Call,
453
457
CheckerContext &C) const ;
454
458
459
+ void preGetdelim (const FnDescription *Desc, const CallEvent &Call,
460
+ CheckerContext &C) const ;
461
+
455
462
void evalGetdelim (const FnDescription *Desc, const CallEvent &Call,
456
463
CheckerContext &C) const ;
457
464
@@ -526,6 +533,14 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
526
533
ProgramStateRef ensureFseekWhenceCorrect (SVal WhenceVal, CheckerContext &C,
527
534
ProgramStateRef State) const ;
528
535
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
+
529
544
// / Generate warning about stream in EOF state.
530
545
// / There will be always a state transition into the passed State,
531
546
// / 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,
1091
1106
C.addTransition (StateFailed);
1092
1107
}
1093
1108
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
+
1094
1226
void StreamChecker::evalGetdelim (const FnDescription *Desc,
1095
1227
const CallEvent &Call,
1096
1228
CheckerContext &C) const {
@@ -1116,6 +1248,20 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
1116
1248
State->BindExpr (E.CE , C.getLocationContext (), RetVal);
1117
1249
StateNotFailed =
1118
1250
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
+ }
1119
1265
if (!StateNotFailed)
1120
1266
return ;
1121
1267
C.addTransition (StateNotFailed);
@@ -1129,6 +1275,11 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
1129
1275
E.isStreamEof () ? ErrorFEof : ErrorFEof | ErrorFError;
1130
1276
StateFailed = E.setStreamState (
1131
1277
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
+ }
1132
1283
if (E.isStreamEof ())
1133
1284
C.addTransition (StateFailed, constructSetEofNoteTag (C, E.StreamSym ));
1134
1285
else
0 commit comments