1 | //===-- StreamChecker.cpp -----------------------------------------*- C++ -*--// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file defines checkers that model and check stream handling functions. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
14 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
15 | #include "clang/StaticAnalyzer/Core/Checker.h" |
16 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
17 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
18 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
19 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
20 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" |
21 | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" |
22 | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" |
23 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" |
24 | #include "llvm/ADT/Sequence.h" |
25 | #include <functional> |
26 | #include <optional> |
27 | |
28 | using namespace clang; |
29 | using namespace ento; |
30 | using namespace std::placeholders; |
31 | |
32 | //===----------------------------------------------------------------------===// |
33 | // Definition of state data structures. |
34 | //===----------------------------------------------------------------------===// |
35 | |
36 | namespace { |
37 | |
38 | struct FnDescription; |
39 | |
40 | /// State of the stream error flags. |
41 | /// Sometimes it is not known to the checker what error flags are set. |
42 | /// This is indicated by setting more than one flag to true. |
43 | /// This is an optimization to avoid state splits. |
44 | /// A stream can either be in FEOF or FERROR but not both at the same time. |
45 | /// Multiple flags are set to handle the corresponding states together. |
46 | struct StreamErrorState { |
47 | /// The stream can be in state where none of the error flags set. |
48 | bool NoError = true; |
49 | /// The stream can be in state where the EOF indicator is set. |
50 | bool FEof = false; |
51 | /// The stream can be in state where the error indicator is set. |
52 | bool FError = false; |
53 | |
54 | bool isNoError() const { return NoError && !FEof && !FError; } |
55 | bool isFEof() const { return !NoError && FEof && !FError; } |
56 | bool isFError() const { return !NoError && !FEof && FError; } |
57 | |
58 | bool operator==(const StreamErrorState &ES) const { |
59 | return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError; |
60 | } |
61 | |
62 | bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); } |
63 | |
64 | StreamErrorState operator|(const StreamErrorState &E) const { |
65 | return {.NoError: NoError || E.NoError, .FEof: FEof || E.FEof, .FError: FError || E.FError}; |
66 | } |
67 | |
68 | StreamErrorState operator&(const StreamErrorState &E) const { |
69 | return {.NoError: NoError && E.NoError, .FEof: FEof && E.FEof, .FError: FError && E.FError}; |
70 | } |
71 | |
72 | StreamErrorState operator~() const { return {.NoError: !NoError, .FEof: !FEof, .FError: !FError}; } |
73 | |
74 | /// Returns if the StreamErrorState is a valid object. |
75 | operator bool() const { return NoError || FEof || FError; } |
76 | |
77 | void Profile(llvm::FoldingSetNodeID &ID) const { |
78 | ID.AddBoolean(B: NoError); |
79 | ID.AddBoolean(B: FEof); |
80 | ID.AddBoolean(B: FError); |
81 | } |
82 | }; |
83 | |
84 | const StreamErrorState ErrorNone{.NoError: true, .FEof: false, .FError: false}; |
85 | const StreamErrorState ErrorFEof{.NoError: false, .FEof: true, .FError: false}; |
86 | const StreamErrorState ErrorFError{.NoError: false, .FEof: false, .FError: true}; |
87 | |
88 | /// Full state information about a stream pointer. |
89 | struct StreamState { |
90 | /// The last file operation called in the stream. |
91 | /// Can be nullptr. |
92 | const FnDescription *LastOperation; |
93 | |
94 | /// State of a stream symbol. |
95 | enum KindTy { |
96 | Opened, /// Stream is opened. |
97 | Closed, /// Closed stream (an invalid stream pointer after it was closed). |
98 | OpenFailed /// The last open operation has failed. |
99 | } State; |
100 | |
101 | /// State of the error flags. |
102 | /// Ignored in non-opened stream state but must be NoError. |
103 | StreamErrorState const ErrorState; |
104 | |
105 | /// Indicate if the file has an "indeterminate file position indicator". |
106 | /// This can be set at a failing read or write or seek operation. |
107 | /// If it is set no more read or write is allowed. |
108 | /// This value is not dependent on the stream error flags: |
109 | /// The error flag may be cleared with `clearerr` but the file position |
110 | /// remains still indeterminate. |
111 | /// This value applies to all error states in ErrorState except FEOF. |
112 | /// An EOF+indeterminate state is the same as EOF state. |
113 | bool const FilePositionIndeterminate = false; |
114 | |
115 | StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES, |
116 | bool IsFilePositionIndeterminate) |
117 | : LastOperation(L), State(S), ErrorState(ES), |
118 | FilePositionIndeterminate(IsFilePositionIndeterminate) { |
119 | assert((!ES.isFEof() || !IsFilePositionIndeterminate) && |
120 | "FilePositionIndeterminate should be false in FEof case." ); |
121 | assert((State == Opened || ErrorState.isNoError()) && |
122 | "ErrorState should be None in non-opened stream state." ); |
123 | } |
124 | |
125 | bool isOpened() const { return State == Opened; } |
126 | bool isClosed() const { return State == Closed; } |
127 | bool isOpenFailed() const { return State == OpenFailed; } |
128 | |
129 | bool operator==(const StreamState &X) const { |
130 | // In not opened state error state should always NoError, so comparison |
131 | // here is no problem. |
132 | return LastOperation == X.LastOperation && State == X.State && |
133 | ErrorState == X.ErrorState && |
134 | FilePositionIndeterminate == X.FilePositionIndeterminate; |
135 | } |
136 | |
137 | static StreamState getOpened(const FnDescription *L, |
138 | const StreamErrorState &ES = ErrorNone, |
139 | bool IsFilePositionIndeterminate = false) { |
140 | return StreamState{L, Opened, ES, IsFilePositionIndeterminate}; |
141 | } |
142 | static StreamState getClosed(const FnDescription *L) { |
143 | return StreamState{L, Closed, {}, false}; |
144 | } |
145 | static StreamState getOpenFailed(const FnDescription *L) { |
146 | return StreamState{L, OpenFailed, {}, false}; |
147 | } |
148 | |
149 | void Profile(llvm::FoldingSetNodeID &ID) const { |
150 | ID.AddPointer(Ptr: LastOperation); |
151 | ID.AddInteger(I: State); |
152 | ErrorState.Profile(ID); |
153 | ID.AddBoolean(B: FilePositionIndeterminate); |
154 | } |
155 | }; |
156 | |
157 | } // namespace |
158 | |
159 | // This map holds the state of a stream. |
160 | // The stream is identified with a SymbolRef that is created when a stream |
161 | // opening function is modeled by the checker. |
162 | REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) |
163 | |
164 | //===----------------------------------------------------------------------===// |
165 | // StreamChecker class and utility functions. |
166 | //===----------------------------------------------------------------------===// |
167 | |
168 | namespace { |
169 | |
170 | class StreamChecker; |
171 | using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, |
172 | const CallEvent &, CheckerContext &)>; |
173 | |
174 | using ArgNoTy = unsigned int; |
175 | static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max(); |
176 | |
177 | const char *FeofNote = "Assuming stream reaches end-of-file here" ; |
178 | const char *FerrorNote = "Assuming this stream operation fails" ; |
179 | |
180 | struct FnDescription { |
181 | FnCheck PreFn; |
182 | FnCheck EvalFn; |
183 | ArgNoTy StreamArgNo; |
184 | }; |
185 | |
186 | /// Get the value of the stream argument out of the passed call event. |
187 | /// The call should contain a function that is described by Desc. |
188 | SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { |
189 | assert(Desc && Desc->StreamArgNo != ArgNone && |
190 | "Try to get a non-existing stream argument." ); |
191 | return Call.getArgSVal(Index: Desc->StreamArgNo); |
192 | } |
193 | |
194 | /// Create a conjured symbol return value for a call expression. |
195 | DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) { |
196 | assert(CE && "Expecting a call expression." ); |
197 | |
198 | const LocationContext *LCtx = C.getLocationContext(); |
199 | return C.getSValBuilder() |
200 | .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) |
201 | .castAs<DefinedSVal>(); |
202 | } |
203 | |
204 | ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, |
205 | const CallExpr *CE) { |
206 | DefinedSVal RetVal = makeRetVal(C, CE); |
207 | State = State->BindExpr(CE, C.getLocationContext(), RetVal); |
208 | State = State->assume(Cond: RetVal, Assumption: true); |
209 | assert(State && "Assumption on new value should not fail." ); |
210 | return State; |
211 | } |
212 | |
213 | ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, |
214 | CheckerContext &C, const CallExpr *CE) { |
215 | State = State->BindExpr(S: CE, LCtx: C.getLocationContext(), |
216 | V: C.getSValBuilder().makeIntVal(Value, CE->getType())); |
217 | return State; |
218 | } |
219 | |
220 | inline void assertStreamStateOpened(const StreamState *SS) { |
221 | assert(SS->isOpened() && "Stream is expected to be opened" ); |
222 | } |
223 | |
224 | class StreamChecker : public Checker<check::PreCall, eval::Call, |
225 | check::DeadSymbols, check::PointerEscape> { |
226 | BugType BT_FileNull{this, "NULL stream pointer" , "Stream handling error" }; |
227 | BugType BT_UseAfterClose{this, "Closed stream" , "Stream handling error" }; |
228 | BugType BT_UseAfterOpenFailed{this, "Invalid stream" , |
229 | "Stream handling error" }; |
230 | BugType BT_IndeterminatePosition{this, "Invalid stream state" , |
231 | "Stream handling error" }; |
232 | BugType BT_IllegalWhence{this, "Illegal whence argument" , |
233 | "Stream handling error" }; |
234 | BugType BT_StreamEof{this, "Stream already in EOF" , "Stream handling error" }; |
235 | BugType BT_ResourceLeak{this, "Resource leak" , "Stream handling error" , |
236 | /*SuppressOnSink =*/true}; |
237 | |
238 | public: |
239 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
240 | bool evalCall(const CallEvent &Call, CheckerContext &C) const; |
241 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
242 | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
243 | const InvalidatedSymbols &Escaped, |
244 | const CallEvent *Call, |
245 | PointerEscapeKind Kind) const; |
246 | |
247 | const BugType *getBT_StreamEof() const { return &BT_StreamEof; } |
248 | const BugType *getBT_IndeterminatePosition() const { |
249 | return &BT_IndeterminatePosition; |
250 | } |
251 | |
252 | const NoteTag *constructSetEofNoteTag(CheckerContext &C, |
253 | SymbolRef StreamSym) const { |
254 | return C.getNoteTag(Cb: [this, StreamSym](PathSensitiveBugReport &BR) { |
255 | if (!BR.isInteresting(sym: StreamSym) || |
256 | &BR.getBugType() != this->getBT_StreamEof()) |
257 | return "" ; |
258 | |
259 | BR.markNotInteresting(sym: StreamSym); |
260 | |
261 | return FeofNote; |
262 | }); |
263 | } |
264 | |
265 | const NoteTag *constructSetErrorNoteTag(CheckerContext &C, |
266 | SymbolRef StreamSym) const { |
267 | return C.getNoteTag(Cb: [this, StreamSym](PathSensitiveBugReport &BR) { |
268 | if (!BR.isInteresting(sym: StreamSym) || |
269 | &BR.getBugType() != this->getBT_IndeterminatePosition()) |
270 | return "" ; |
271 | |
272 | BR.markNotInteresting(sym: StreamSym); |
273 | |
274 | return FerrorNote; |
275 | }); |
276 | } |
277 | |
278 | const NoteTag *constructSetEofOrErrorNoteTag(CheckerContext &C, |
279 | SymbolRef StreamSym) const { |
280 | return C.getNoteTag(Cb: [this, StreamSym](PathSensitiveBugReport &BR) { |
281 | if (!BR.isInteresting(sym: StreamSym)) |
282 | return "" ; |
283 | |
284 | if (&BR.getBugType() == this->getBT_StreamEof()) { |
285 | BR.markNotInteresting(sym: StreamSym); |
286 | return FeofNote; |
287 | } |
288 | if (&BR.getBugType() == this->getBT_IndeterminatePosition()) { |
289 | BR.markNotInteresting(sym: StreamSym); |
290 | return FerrorNote; |
291 | } |
292 | |
293 | return "" ; |
294 | }); |
295 | } |
296 | |
297 | /// If true, evaluate special testing stream functions. |
298 | bool TestMode = false; |
299 | |
300 | /// If true, generate failure branches for cases that are often not checked. |
301 | bool PedanticMode = false; |
302 | |
303 | private: |
304 | CallDescriptionMap<FnDescription> FnDescriptions = { |
305 | {{CDM::CLibrary, {"fopen" }, 2}, |
306 | {.PreFn: nullptr, .EvalFn: &StreamChecker::evalFopen, .StreamArgNo: ArgNone}}, |
307 | {{CDM::CLibrary, {"fdopen" }, 2}, |
308 | {.PreFn: nullptr, .EvalFn: &StreamChecker::evalFopen, .StreamArgNo: ArgNone}}, |
309 | {{CDM::CLibrary, {"freopen" }, 3}, |
310 | {.PreFn: &StreamChecker::preFreopen, .EvalFn: &StreamChecker::evalFreopen, .StreamArgNo: 2}}, |
311 | {{CDM::CLibrary, {"tmpfile" }, 0}, |
312 | {.PreFn: nullptr, .EvalFn: &StreamChecker::evalFopen, .StreamArgNo: ArgNone}}, |
313 | {{CDM::CLibrary, {"fclose" }, 1}, |
314 | {.PreFn: &StreamChecker::preDefault, .EvalFn: &StreamChecker::evalFclose, .StreamArgNo: 0}}, |
315 | {{CDM::CLibrary, {"fread" }, 4}, |
316 | {.PreFn: &StreamChecker::preRead, |
317 | .EvalFn: std::bind(f: &StreamChecker::evalFreadFwrite, args: _1, args: _2, args: _3, args: _4, args: true), .StreamArgNo: 3}}, |
318 | {{CDM::CLibrary, {"fwrite" }, 4}, |
319 | {.PreFn: &StreamChecker::preWrite, |
320 | .EvalFn: std::bind(f: &StreamChecker::evalFreadFwrite, args: _1, args: _2, args: _3, args: _4, args: false), .StreamArgNo: 3}}, |
321 | {{CDM::CLibrary, {"fgetc" }, 1}, |
322 | {.PreFn: &StreamChecker::preRead, |
323 | .EvalFn: std::bind(f: &StreamChecker::evalFgetx, args: _1, args: _2, args: _3, args: _4, args: true), .StreamArgNo: 0}}, |
324 | {{CDM::CLibrary, {"fgets" }, 3}, |
325 | {.PreFn: &StreamChecker::preRead, |
326 | .EvalFn: std::bind(f: &StreamChecker::evalFgetx, args: _1, args: _2, args: _3, args: _4, args: false), .StreamArgNo: 2}}, |
327 | {{CDM::CLibrary, {"getc" }, 1}, |
328 | {.PreFn: &StreamChecker::preRead, |
329 | .EvalFn: std::bind(f: &StreamChecker::evalFgetx, args: _1, args: _2, args: _3, args: _4, args: true), .StreamArgNo: 0}}, |
330 | {{CDM::CLibrary, {"fputc" }, 2}, |
331 | {.PreFn: &StreamChecker::preWrite, |
332 | .EvalFn: std::bind(f: &StreamChecker::evalFputx, args: _1, args: _2, args: _3, args: _4, args: true), .StreamArgNo: 1}}, |
333 | {{CDM::CLibrary, {"fputs" }, 2}, |
334 | {.PreFn: &StreamChecker::preWrite, |
335 | .EvalFn: std::bind(f: &StreamChecker::evalFputx, args: _1, args: _2, args: _3, args: _4, args: false), .StreamArgNo: 1}}, |
336 | {{CDM::CLibrary, {"putc" }, 2}, |
337 | {.PreFn: &StreamChecker::preWrite, |
338 | .EvalFn: std::bind(f: &StreamChecker::evalFputx, args: _1, args: _2, args: _3, args: _4, args: true), .StreamArgNo: 1}}, |
339 | {{CDM::CLibrary, {"fprintf" }}, |
340 | {.PreFn: &StreamChecker::preWrite, |
341 | .EvalFn: std::bind(f: &StreamChecker::evalFprintf, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 0}}, |
342 | {{CDM::CLibrary, {"vfprintf" }, 3}, |
343 | {.PreFn: &StreamChecker::preWrite, |
344 | .EvalFn: std::bind(f: &StreamChecker::evalFprintf, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 0}}, |
345 | {{CDM::CLibrary, {"fscanf" }}, |
346 | {.PreFn: &StreamChecker::preRead, |
347 | .EvalFn: std::bind(f: &StreamChecker::evalFscanf, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 0}}, |
348 | {{CDM::CLibrary, {"vfscanf" }, 3}, |
349 | {.PreFn: &StreamChecker::preRead, |
350 | .EvalFn: std::bind(f: &StreamChecker::evalFscanf, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 0}}, |
351 | {{CDM::CLibrary, {"ungetc" }, 2}, |
352 | {.PreFn: &StreamChecker::preWrite, |
353 | .EvalFn: std::bind(f: &StreamChecker::evalUngetc, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 1}}, |
354 | {{CDM::CLibrary, {"getdelim" }, 4}, |
355 | {.PreFn: &StreamChecker::preRead, |
356 | .EvalFn: std::bind(f: &StreamChecker::evalGetdelim, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 3}}, |
357 | {{CDM::CLibrary, {"getline" }, 3}, |
358 | {.PreFn: &StreamChecker::preRead, |
359 | .EvalFn: std::bind(f: &StreamChecker::evalGetdelim, args: _1, args: _2, args: _3, args: _4), .StreamArgNo: 2}}, |
360 | {{CDM::CLibrary, {"fseek" }, 3}, |
361 | {.PreFn: &StreamChecker::preFseek, .EvalFn: &StreamChecker::evalFseek, .StreamArgNo: 0}}, |
362 | {{CDM::CLibrary, {"fseeko" }, 3}, |
363 | {.PreFn: &StreamChecker::preFseek, .EvalFn: &StreamChecker::evalFseek, .StreamArgNo: 0}}, |
364 | {{CDM::CLibrary, {"ftell" }, 1}, |
365 | {.PreFn: &StreamChecker::preWrite, .EvalFn: &StreamChecker::evalFtell, .StreamArgNo: 0}}, |
366 | {{CDM::CLibrary, {"ftello" }, 1}, |
367 | {.PreFn: &StreamChecker::preWrite, .EvalFn: &StreamChecker::evalFtell, .StreamArgNo: 0}}, |
368 | {{CDM::CLibrary, {"fflush" }, 1}, |
369 | {.PreFn: &StreamChecker::preFflush, .EvalFn: &StreamChecker::evalFflush, .StreamArgNo: 0}}, |
370 | {{CDM::CLibrary, {"rewind" }, 1}, |
371 | {.PreFn: &StreamChecker::preDefault, .EvalFn: &StreamChecker::evalRewind, .StreamArgNo: 0}}, |
372 | {{CDM::CLibrary, {"fgetpos" }, 2}, |
373 | {.PreFn: &StreamChecker::preWrite, .EvalFn: &StreamChecker::evalFgetpos, .StreamArgNo: 0}}, |
374 | {{CDM::CLibrary, {"fsetpos" }, 2}, |
375 | {.PreFn: &StreamChecker::preDefault, .EvalFn: &StreamChecker::evalFsetpos, .StreamArgNo: 0}}, |
376 | {{CDM::CLibrary, {"clearerr" }, 1}, |
377 | {.PreFn: &StreamChecker::preDefault, .EvalFn: &StreamChecker::evalClearerr, .StreamArgNo: 0}}, |
378 | {{CDM::CLibrary, {"feof" }, 1}, |
379 | {.PreFn: &StreamChecker::preDefault, |
380 | .EvalFn: std::bind(f: &StreamChecker::evalFeofFerror, args: _1, args: _2, args: _3, args: _4, args: ErrorFEof), |
381 | .StreamArgNo: 0}}, |
382 | {{CDM::CLibrary, {"ferror" }, 1}, |
383 | {.PreFn: &StreamChecker::preDefault, |
384 | .EvalFn: std::bind(f: &StreamChecker::evalFeofFerror, args: _1, args: _2, args: _3, args: _4, args: ErrorFError), |
385 | .StreamArgNo: 0}}, |
386 | {{CDM::CLibrary, {"fileno" }, 1}, |
387 | {.PreFn: &StreamChecker::preDefault, .EvalFn: &StreamChecker::evalFileno, .StreamArgNo: 0}}, |
388 | }; |
389 | |
390 | CallDescriptionMap<FnDescription> FnTestDescriptions = { |
391 | {{{"StreamTesterChecker_make_feof_stream" }, 1}, |
392 | {.PreFn: nullptr, |
393 | .EvalFn: std::bind(f: &StreamChecker::evalSetFeofFerror, args: _1, args: _2, args: _3, args: _4, args: ErrorFEof, |
394 | args: false), |
395 | .StreamArgNo: 0}}, |
396 | {{{"StreamTesterChecker_make_ferror_stream" }, 1}, |
397 | {.PreFn: nullptr, |
398 | .EvalFn: std::bind(f: &StreamChecker::evalSetFeofFerror, args: _1, args: _2, args: _3, args: _4, |
399 | args: ErrorFError, args: false), |
400 | .StreamArgNo: 0}}, |
401 | {{{"StreamTesterChecker_make_ferror_indeterminate_stream" }, 1}, |
402 | {.PreFn: nullptr, |
403 | .EvalFn: std::bind(f: &StreamChecker::evalSetFeofFerror, args: _1, args: _2, args: _3, args: _4, |
404 | args: ErrorFError, args: true), |
405 | .StreamArgNo: 0}}, |
406 | }; |
407 | |
408 | /// Expanded value of EOF, empty before initialization. |
409 | mutable std::optional<int> EofVal; |
410 | /// Expanded value of SEEK_SET, 0 if not found. |
411 | mutable int SeekSetVal = 0; |
412 | /// Expanded value of SEEK_CUR, 1 if not found. |
413 | mutable int SeekCurVal = 1; |
414 | /// Expanded value of SEEK_END, 2 if not found. |
415 | mutable int SeekEndVal = 2; |
416 | /// The built-in va_list type is platform-specific |
417 | mutable QualType VaListType; |
418 | |
419 | void evalFopen(const FnDescription *Desc, const CallEvent &Call, |
420 | CheckerContext &C) const; |
421 | |
422 | void preFreopen(const FnDescription *Desc, const CallEvent &Call, |
423 | CheckerContext &C) const; |
424 | void evalFreopen(const FnDescription *Desc, const CallEvent &Call, |
425 | CheckerContext &C) const; |
426 | |
427 | void evalFclose(const FnDescription *Desc, const CallEvent &Call, |
428 | CheckerContext &C) const; |
429 | |
430 | void preRead(const FnDescription *Desc, const CallEvent &Call, |
431 | CheckerContext &C) const; |
432 | |
433 | void preWrite(const FnDescription *Desc, const CallEvent &Call, |
434 | CheckerContext &C) const; |
435 | |
436 | void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, |
437 | CheckerContext &C, bool IsFread) const; |
438 | |
439 | void evalFgetx(const FnDescription *Desc, const CallEvent &Call, |
440 | CheckerContext &C, bool SingleChar) const; |
441 | |
442 | void evalFputx(const FnDescription *Desc, const CallEvent &Call, |
443 | CheckerContext &C, bool IsSingleChar) const; |
444 | |
445 | void evalFprintf(const FnDescription *Desc, const CallEvent &Call, |
446 | CheckerContext &C) const; |
447 | |
448 | void evalFscanf(const FnDescription *Desc, const CallEvent &Call, |
449 | CheckerContext &C) const; |
450 | |
451 | void evalUngetc(const FnDescription *Desc, const CallEvent &Call, |
452 | CheckerContext &C) const; |
453 | |
454 | void evalGetdelim(const FnDescription *Desc, const CallEvent &Call, |
455 | CheckerContext &C) const; |
456 | |
457 | void preFseek(const FnDescription *Desc, const CallEvent &Call, |
458 | CheckerContext &C) const; |
459 | void evalFseek(const FnDescription *Desc, const CallEvent &Call, |
460 | CheckerContext &C) const; |
461 | |
462 | void evalFgetpos(const FnDescription *Desc, const CallEvent &Call, |
463 | CheckerContext &C) const; |
464 | |
465 | void evalFsetpos(const FnDescription *Desc, const CallEvent &Call, |
466 | CheckerContext &C) const; |
467 | |
468 | void evalFtell(const FnDescription *Desc, const CallEvent &Call, |
469 | CheckerContext &C) const; |
470 | |
471 | void evalRewind(const FnDescription *Desc, const CallEvent &Call, |
472 | CheckerContext &C) const; |
473 | |
474 | void preDefault(const FnDescription *Desc, const CallEvent &Call, |
475 | CheckerContext &C) const; |
476 | |
477 | void evalClearerr(const FnDescription *Desc, const CallEvent &Call, |
478 | CheckerContext &C) const; |
479 | |
480 | void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, |
481 | CheckerContext &C, |
482 | const StreamErrorState &ErrorKind) const; |
483 | |
484 | void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, |
485 | CheckerContext &C, const StreamErrorState &ErrorKind, |
486 | bool Indeterminate) const; |
487 | |
488 | void preFflush(const FnDescription *Desc, const CallEvent &Call, |
489 | CheckerContext &C) const; |
490 | |
491 | void evalFflush(const FnDescription *Desc, const CallEvent &Call, |
492 | CheckerContext &C) const; |
493 | |
494 | void evalFileno(const FnDescription *Desc, const CallEvent &Call, |
495 | CheckerContext &C) const; |
496 | |
497 | /// Check that the stream (in StreamVal) is not NULL. |
498 | /// If it can only be NULL a fatal error is emitted and nullptr returned. |
499 | /// Otherwise the return value is a new state where the stream is constrained |
500 | /// to be non-null. |
501 | ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, |
502 | CheckerContext &C, |
503 | ProgramStateRef State) const; |
504 | |
505 | /// Check that the stream is the opened state. |
506 | /// If the stream is known to be not opened an error is generated |
507 | /// and nullptr returned, otherwise the original state is returned. |
508 | ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, |
509 | ProgramStateRef State) const; |
510 | |
511 | /// Check that the stream has not an invalid ("indeterminate") file position, |
512 | /// generate warning for it. |
513 | /// (EOF is not an invalid position.) |
514 | /// The returned state can be nullptr if a fatal error was generated. |
515 | /// It can return non-null state if the stream has not an invalid position or |
516 | /// there is execution path with non-invalid position. |
517 | ProgramStateRef |
518 | ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, |
519 | ProgramStateRef State) const; |
520 | |
521 | /// Check the legality of the 'whence' argument of 'fseek'. |
522 | /// Generate error and return nullptr if it is found to be illegal. |
523 | /// Otherwise returns the state. |
524 | /// (State is not changed here because the "whence" value is already known.) |
525 | ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, |
526 | ProgramStateRef State) const; |
527 | |
528 | /// Generate warning about stream in EOF state. |
529 | /// There will be always a state transition into the passed State, |
530 | /// by the new non-fatal error node or (if failed) a normal transition, |
531 | /// to ensure uniform handling. |
532 | void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, |
533 | ProgramStateRef State) const; |
534 | |
535 | /// Emit resource leak warnings for the given symbols. |
536 | /// Createn a non-fatal error node for these, and returns it (if any warnings |
537 | /// were generated). Return value is non-null. |
538 | ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, |
539 | CheckerContext &C, ExplodedNode *Pred) const; |
540 | |
541 | /// Find the description data of the function called by a call event. |
542 | /// Returns nullptr if no function is recognized. |
543 | const FnDescription *lookupFn(const CallEvent &Call) const { |
544 | // Recognize "global C functions" with only integral or pointer arguments |
545 | // (and matching name) as stream functions. |
546 | for (auto *P : Call.parameters()) { |
547 | QualType T = P->getType(); |
548 | if (!T->isIntegralOrEnumerationType() && !T->isPointerType() && |
549 | T.getCanonicalType() != VaListType) |
550 | return nullptr; |
551 | } |
552 | |
553 | return FnDescriptions.lookup(Call); |
554 | } |
555 | |
556 | /// Generate a message for BugReporterVisitor if the stored symbol is |
557 | /// marked as interesting by the actual bug report. |
558 | const NoteTag *constructLeakNoteTag(CheckerContext &C, SymbolRef StreamSym, |
559 | const std::string &Message) const { |
560 | return C.getNoteTag(Cb: [this, StreamSym, |
561 | Message](PathSensitiveBugReport &BR) -> std::string { |
562 | if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak) |
563 | return Message; |
564 | return "" ; |
565 | }); |
566 | } |
567 | |
568 | void initMacroValues(CheckerContext &C) const { |
569 | if (EofVal) |
570 | return; |
571 | |
572 | if (const std::optional<int> OptInt = |
573 | tryExpandAsInteger(Macro: "EOF" , PP: C.getPreprocessor())) |
574 | EofVal = *OptInt; |
575 | else |
576 | EofVal = -1; |
577 | if (const std::optional<int> OptInt = |
578 | tryExpandAsInteger(Macro: "SEEK_SET" , PP: C.getPreprocessor())) |
579 | SeekSetVal = *OptInt; |
580 | if (const std::optional<int> OptInt = |
581 | tryExpandAsInteger(Macro: "SEEK_END" , PP: C.getPreprocessor())) |
582 | SeekEndVal = *OptInt; |
583 | if (const std::optional<int> OptInt = |
584 | tryExpandAsInteger(Macro: "SEEK_CUR" , PP: C.getPreprocessor())) |
585 | SeekCurVal = *OptInt; |
586 | } |
587 | |
588 | void initVaListType(CheckerContext &C) const { |
589 | VaListType = C.getASTContext().getBuiltinVaListType().getCanonicalType(); |
590 | } |
591 | |
592 | /// Searches for the ExplodedNode where the file descriptor was acquired for |
593 | /// StreamSym. |
594 | static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, |
595 | SymbolRef StreamSym, |
596 | CheckerContext &C); |
597 | }; |
598 | |
599 | struct StreamOperationEvaluator { |
600 | SValBuilder &SVB; |
601 | const ASTContext &ACtx; |
602 | |
603 | SymbolRef StreamSym = nullptr; |
604 | const StreamState *SS = nullptr; |
605 | const CallExpr *CE = nullptr; |
606 | StreamErrorState NewES; |
607 | |
608 | StreamOperationEvaluator(CheckerContext &C) |
609 | : SVB(C.getSValBuilder()), ACtx(C.getASTContext()) { |
610 | ; |
611 | } |
612 | |
613 | bool Init(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C, |
614 | ProgramStateRef State) { |
615 | StreamSym = getStreamArg(Desc, Call).getAsSymbol(); |
616 | if (!StreamSym) |
617 | return false; |
618 | SS = State->get<StreamMap>(key: StreamSym); |
619 | if (!SS) |
620 | return false; |
621 | NewES = SS->ErrorState; |
622 | CE = dyn_cast_or_null<CallExpr>(Val: Call.getOriginExpr()); |
623 | if (!CE) |
624 | return false; |
625 | |
626 | assertStreamStateOpened(SS); |
627 | |
628 | return true; |
629 | } |
630 | |
631 | bool isStreamEof() const { return SS->ErrorState == ErrorFEof; } |
632 | |
633 | NonLoc getZeroVal(const CallEvent &Call) { |
634 | return *SVB.makeZeroVal(type: Call.getResultType()).getAs<NonLoc>(); |
635 | } |
636 | |
637 | ProgramStateRef setStreamState(ProgramStateRef State, |
638 | const StreamState &NewSS) { |
639 | NewES = NewSS.ErrorState; |
640 | return State->set<StreamMap>(K: StreamSym, E: NewSS); |
641 | } |
642 | |
643 | ProgramStateRef makeAndBindRetVal(ProgramStateRef State, CheckerContext &C) { |
644 | NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); |
645 | return State->BindExpr(CE, C.getLocationContext(), RetVal); |
646 | } |
647 | |
648 | ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C, |
649 | uint64_t Val) { |
650 | return State->BindExpr(CE, C.getLocationContext(), |
651 | SVB.makeIntVal(integer: Val, type: CE->getCallReturnType(Ctx: ACtx))); |
652 | } |
653 | |
654 | ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C, |
655 | SVal Val) { |
656 | return State->BindExpr(CE, C.getLocationContext(), Val); |
657 | } |
658 | |
659 | ProgramStateRef bindNullReturnValue(ProgramStateRef State, |
660 | CheckerContext &C) { |
661 | return State->BindExpr(S: CE, LCtx: C.getLocationContext(), |
662 | V: C.getSValBuilder().makeNullWithType(type: CE->getType())); |
663 | } |
664 | |
665 | ProgramStateRef assumeBinOpNN(ProgramStateRef State, |
666 | BinaryOperator::Opcode Op, NonLoc LHS, |
667 | NonLoc RHS) { |
668 | auto Cond = SVB.evalBinOpNN(state: State, op: Op, lhs: LHS, rhs: RHS, resultTy: SVB.getConditionType()) |
669 | .getAs<DefinedOrUnknownSVal>(); |
670 | if (!Cond) |
671 | return nullptr; |
672 | return State->assume(Cond: *Cond, Assumption: true); |
673 | } |
674 | |
675 | ConstraintManager::ProgramStatePair |
676 | makeRetValAndAssumeDual(ProgramStateRef State, CheckerContext &C) { |
677 | DefinedSVal RetVal = makeRetVal(C, CE); |
678 | State = State->BindExpr(CE, C.getLocationContext(), RetVal); |
679 | return C.getConstraintManager().assumeDual(State, Cond: RetVal); |
680 | } |
681 | |
682 | const NoteTag *getFailureNoteTag(const StreamChecker *Ch, CheckerContext &C) { |
683 | bool SetFeof = NewES.FEof && !SS->ErrorState.FEof; |
684 | bool SetFerror = NewES.FError && !SS->ErrorState.FError; |
685 | if (SetFeof && !SetFerror) |
686 | return Ch->constructSetEofNoteTag(C, StreamSym); |
687 | if (!SetFeof && SetFerror) |
688 | return Ch->constructSetErrorNoteTag(C, StreamSym); |
689 | if (SetFeof && SetFerror) |
690 | return Ch->constructSetEofOrErrorNoteTag(C, StreamSym); |
691 | return nullptr; |
692 | } |
693 | }; |
694 | |
695 | } // end anonymous namespace |
696 | |
697 | const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, |
698 | SymbolRef StreamSym, |
699 | CheckerContext &C) { |
700 | ProgramStateRef State = N->getState(); |
701 | // When bug type is resource leak, exploded node N may not have state info |
702 | // for leaked file descriptor, but predecessor should have it. |
703 | if (!State->get<StreamMap>(key: StreamSym)) |
704 | N = N->getFirstPred(); |
705 | |
706 | const ExplodedNode *Pred = N; |
707 | while (N) { |
708 | State = N->getState(); |
709 | if (!State->get<StreamMap>(key: StreamSym)) |
710 | return Pred; |
711 | Pred = N; |
712 | N = N->getFirstPred(); |
713 | } |
714 | |
715 | return nullptr; |
716 | } |
717 | |
718 | static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C, |
719 | const CallEvent &Call, |
720 | ArrayRef<unsigned int> EscapingArgs) { |
721 | const auto *CE = Call.getOriginExpr(); |
722 | |
723 | SmallVector<SVal> EscapingVals; |
724 | EscapingVals.reserve(N: EscapingArgs.size()); |
725 | for (auto EscArgIdx : EscapingArgs) |
726 | EscapingVals.push_back(Elt: Call.getArgSVal(Index: EscArgIdx)); |
727 | State = State->invalidateRegions(Regions: EscapingVals, E: CE, BlockCount: C.blockCount(), |
728 | LCtx: C.getLocationContext(), |
729 | /*CausesPointerEscape=*/false); |
730 | return State; |
731 | } |
732 | |
733 | //===----------------------------------------------------------------------===// |
734 | // Methods of StreamChecker. |
735 | //===----------------------------------------------------------------------===// |
736 | |
737 | void StreamChecker::checkPreCall(const CallEvent &Call, |
738 | CheckerContext &C) const { |
739 | initMacroValues(C); |
740 | initVaListType(C); |
741 | |
742 | const FnDescription *Desc = lookupFn(Call); |
743 | if (!Desc || !Desc->PreFn) |
744 | return; |
745 | |
746 | Desc->PreFn(this, Desc, Call, C); |
747 | } |
748 | |
749 | bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { |
750 | const FnDescription *Desc = lookupFn(Call); |
751 | if (!Desc && TestMode) |
752 | Desc = FnTestDescriptions.lookup(Call); |
753 | if (!Desc || !Desc->EvalFn) |
754 | return false; |
755 | |
756 | Desc->EvalFn(this, Desc, Call, C); |
757 | |
758 | return C.isDifferent(); |
759 | } |
760 | |
761 | void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, |
762 | CheckerContext &C) const { |
763 | ProgramStateRef State = C.getState(); |
764 | const CallExpr *CE = dyn_cast_or_null<CallExpr>(Val: Call.getOriginExpr()); |
765 | if (!CE) |
766 | return; |
767 | |
768 | DefinedSVal RetVal = makeRetVal(C, CE); |
769 | SymbolRef RetSym = RetVal.getAsSymbol(); |
770 | assert(RetSym && "RetVal must be a symbol here." ); |
771 | |
772 | State = State->BindExpr(CE, C.getLocationContext(), RetVal); |
773 | |
774 | // Bifurcate the state into two: one with a valid FILE* pointer, the other |
775 | // with a NULL. |
776 | ProgramStateRef StateNotNull, StateNull; |
777 | std::tie(args&: StateNotNull, args&: StateNull) = |
778 | C.getConstraintManager().assumeDual(State, Cond: RetVal); |
779 | |
780 | StateNotNull = |
781 | StateNotNull->set<StreamMap>(K: RetSym, E: StreamState::getOpened(L: Desc)); |
782 | StateNull = |
783 | StateNull->set<StreamMap>(K: RetSym, E: StreamState::getOpenFailed(L: Desc)); |
784 | |
785 | C.addTransition(State: StateNotNull, |
786 | Tag: constructLeakNoteTag(C, StreamSym: RetSym, Message: "Stream opened here" )); |
787 | C.addTransition(State: StateNull); |
788 | } |
789 | |
790 | void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, |
791 | CheckerContext &C) const { |
792 | // Do not allow NULL as passed stream pointer but allow a closed stream. |
793 | ProgramStateRef State = C.getState(); |
794 | State = ensureStreamNonNull(StreamVal: getStreamArg(Desc, Call), |
795 | StreamE: Call.getArgExpr(Index: Desc->StreamArgNo), C, State); |
796 | if (!State) |
797 | return; |
798 | |
799 | C.addTransition(State); |
800 | } |
801 | |
802 | void StreamChecker::evalFreopen(const FnDescription *Desc, |
803 | const CallEvent &Call, |
804 | CheckerContext &C) const { |
805 | ProgramStateRef State = C.getState(); |
806 | |
807 | auto *CE = dyn_cast_or_null<CallExpr>(Val: Call.getOriginExpr()); |
808 | if (!CE) |
809 | return; |
810 | |
811 | std::optional<DefinedSVal> StreamVal = |
812 | getStreamArg(Desc, Call).getAs<DefinedSVal>(); |
813 | if (!StreamVal) |
814 | return; |
815 | |
816 | SymbolRef StreamSym = StreamVal->getAsSymbol(); |
817 | // Do not care about concrete values for stream ("(FILE *)0x12345"?). |
818 | // FIXME: Can be stdin, stdout, stderr such values? |
819 | if (!StreamSym) |
820 | return; |
821 | |
822 | // Do not handle untracked stream. It is probably escaped. |
823 | if (!State->get<StreamMap>(key: StreamSym)) |
824 | return; |
825 | |
826 | // Generate state for non-failed case. |
827 | // Return value is the passed stream pointer. |
828 | // According to the documentations, the stream is closed first |
829 | // but any close error is ignored. The state changes to (or remains) opened. |
830 | ProgramStateRef StateRetNotNull = |
831 | State->BindExpr(CE, C.getLocationContext(), *StreamVal); |
832 | // Generate state for NULL return value. |
833 | // Stream switches to OpenFailed state. |
834 | ProgramStateRef StateRetNull = |
835 | State->BindExpr(S: CE, LCtx: C.getLocationContext(), |
836 | V: C.getSValBuilder().makeNullWithType(type: CE->getType())); |
837 | |
838 | StateRetNotNull = |
839 | StateRetNotNull->set<StreamMap>(K: StreamSym, E: StreamState::getOpened(L: Desc)); |
840 | StateRetNull = |
841 | StateRetNull->set<StreamMap>(K: StreamSym, E: StreamState::getOpenFailed(L: Desc)); |
842 | |
843 | C.addTransition(State: StateRetNotNull, |
844 | Tag: constructLeakNoteTag(C, StreamSym, Message: "Stream reopened here" )); |
845 | C.addTransition(State: StateRetNull); |
846 | } |
847 | |
848 | void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, |
849 | CheckerContext &C) const { |
850 | ProgramStateRef State = C.getState(); |
851 | StreamOperationEvaluator E(C); |
852 | if (!E.Init(Desc, Call, C, State)) |
853 | return; |
854 | |
855 | // Close the File Descriptor. |
856 | // Regardless if the close fails or not, stream becomes "closed" |
857 | // and can not be used any more. |
858 | State = E.setStreamState(State, NewSS: StreamState::getClosed(L: Desc)); |
859 | |
860 | // Return 0 on success, EOF on failure. |
861 | C.addTransition(State: E.bindReturnValue(State, C, Val: 0)); |
862 | C.addTransition(State: E.bindReturnValue(State, C, Val: *EofVal)); |
863 | } |
864 | |
865 | void StreamChecker::preRead(const FnDescription *Desc, const CallEvent &Call, |
866 | CheckerContext &C) const { |
867 | ProgramStateRef State = C.getState(); |
868 | SVal StreamVal = getStreamArg(Desc, Call); |
869 | State = ensureStreamNonNull(StreamVal, StreamE: Call.getArgExpr(Index: Desc->StreamArgNo), C, |
870 | State); |
871 | if (!State) |
872 | return; |
873 | State = ensureStreamOpened(StreamVal, C, State); |
874 | if (!State) |
875 | return; |
876 | State = ensureNoFilePositionIndeterminate(StreamVal, C, State); |
877 | if (!State) |
878 | return; |
879 | |
880 | SymbolRef Sym = StreamVal.getAsSymbol(); |
881 | if (Sym && State->get<StreamMap>(key: Sym)) { |
882 | const StreamState *SS = State->get<StreamMap>(key: Sym); |
883 | if (SS->ErrorState & ErrorFEof) |
884 | reportFEofWarning(StreamSym: Sym, C, State); |
885 | } else { |
886 | C.addTransition(State); |
887 | } |
888 | } |
889 | |
890 | void StreamChecker::preWrite(const FnDescription *Desc, const CallEvent &Call, |
891 | CheckerContext &C) const { |
892 | ProgramStateRef State = C.getState(); |
893 | SVal StreamVal = getStreamArg(Desc, Call); |
894 | State = ensureStreamNonNull(StreamVal, StreamE: Call.getArgExpr(Index: Desc->StreamArgNo), C, |
895 | State); |
896 | if (!State) |
897 | return; |
898 | State = ensureStreamOpened(StreamVal, C, State); |
899 | if (!State) |
900 | return; |
901 | State = ensureNoFilePositionIndeterminate(StreamVal, C, State); |
902 | if (!State) |
903 | return; |
904 | |
905 | C.addTransition(State); |
906 | } |
907 | |
908 | void StreamChecker::evalFreadFwrite(const FnDescription *Desc, |
909 | const CallEvent &Call, CheckerContext &C, |
910 | bool IsFread) const { |
911 | ProgramStateRef State = C.getState(); |
912 | StreamOperationEvaluator E(C); |
913 | if (!E.Init(Desc, Call, C, State)) |
914 | return; |
915 | |
916 | std::optional<NonLoc> SizeVal = Call.getArgSVal(Index: 1).getAs<NonLoc>(); |
917 | if (!SizeVal) |
918 | return; |
919 | std::optional<NonLoc> NMembVal = Call.getArgSVal(Index: 2).getAs<NonLoc>(); |
920 | if (!NMembVal) |
921 | return; |
922 | |
923 | // C'99 standard, §7.19.8.1.3, the return value of fread: |
924 | // The fread function returns the number of elements successfully read, which |
925 | // may be less than nmemb if a read error or end-of-file is encountered. If |
926 | // size or nmemb is zero, fread returns zero and the contents of the array and |
927 | // the state of the stream remain unchanged. |
928 | if (State->isNull(V: *SizeVal).isConstrainedTrue() || |
929 | State->isNull(V: *NMembVal).isConstrainedTrue()) { |
930 | // This is the "size or nmemb is zero" case. |
931 | // Just return 0, do nothing more (not clear the error flags). |
932 | C.addTransition(State: E.bindReturnValue(State, C, Val: 0)); |
933 | return; |
934 | } |
935 | |
936 | // At read, invalidate the buffer in any case of error or success, |
937 | // except if EOF was already present. |
938 | if (IsFread && !E.isStreamEof()) |
939 | State = escapeArgs(State, C, Call, EscapingArgs: {0}); |
940 | |
941 | // Generate a transition for the success state. |
942 | // If we know the state to be FEOF at fread, do not add a success state. |
943 | if (!IsFread || !E.isStreamEof()) { |
944 | ProgramStateRef StateNotFailed = |
945 | State->BindExpr(E.CE, C.getLocationContext(), *NMembVal); |
946 | StateNotFailed = |
947 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
948 | C.addTransition(State: StateNotFailed); |
949 | } |
950 | |
951 | // Add transition for the failed state. |
952 | // At write, add failure case only if "pedantic mode" is on. |
953 | if (!IsFread && !PedanticMode) |
954 | return; |
955 | |
956 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
957 | ProgramStateRef StateFailed = |
958 | State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
959 | StateFailed = E.assumeBinOpNN(State: StateFailed, Op: BO_LT, LHS: RetVal, RHS: *NMembVal); |
960 | if (!StateFailed) |
961 | return; |
962 | |
963 | StreamErrorState NewES; |
964 | if (IsFread) |
965 | NewES = E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError; |
966 | else |
967 | NewES = ErrorFError; |
968 | // If a (non-EOF) error occurs, the resulting value of the file position |
969 | // indicator for the stream is indeterminate. |
970 | StateFailed = E.setStreamState( |
971 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: NewES, IsFilePositionIndeterminate: !NewES.isFEof())); |
972 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
973 | } |
974 | |
975 | void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call, |
976 | CheckerContext &C, bool SingleChar) const { |
977 | // `fgetc` returns the read character on success, otherwise returns EOF. |
978 | // `fgets` returns the read buffer address on success, otherwise returns NULL. |
979 | |
980 | ProgramStateRef State = C.getState(); |
981 | StreamOperationEvaluator E(C); |
982 | if (!E.Init(Desc, Call, C, State)) |
983 | return; |
984 | |
985 | if (!E.isStreamEof()) { |
986 | // If there was already EOF, assume that read buffer is not changed. |
987 | // Otherwise it may change at success or failure. |
988 | State = escapeArgs(State, C, Call, EscapingArgs: {0}); |
989 | if (SingleChar) { |
990 | // Generate a transition for the success state of `fgetc`. |
991 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
992 | ProgramStateRef StateNotFailed = |
993 | State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
994 | // The returned 'unsigned char' of `fgetc` is converted to 'int', |
995 | // so we need to check if it is in range [0, 255]. |
996 | StateNotFailed = StateNotFailed->assumeInclusiveRange( |
997 | RetVal, |
998 | E.SVB.getBasicValueFactory().getValue(0, E.ACtx.UnsignedCharTy), |
999 | E.SVB.getBasicValueFactory().getMaxValue(E.ACtx.UnsignedCharTy), |
1000 | true); |
1001 | if (!StateNotFailed) |
1002 | return; |
1003 | C.addTransition(State: StateNotFailed); |
1004 | } else { |
1005 | // Generate a transition for the success state of `fgets`. |
1006 | std::optional<DefinedSVal> GetBuf = |
1007 | Call.getArgSVal(Index: 0).getAs<DefinedSVal>(); |
1008 | if (!GetBuf) |
1009 | return; |
1010 | ProgramStateRef StateNotFailed = |
1011 | State->BindExpr(E.CE, C.getLocationContext(), *GetBuf); |
1012 | StateNotFailed = |
1013 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
1014 | C.addTransition(State: StateNotFailed); |
1015 | } |
1016 | } |
1017 | |
1018 | // Add transition for the failed state. |
1019 | ProgramStateRef StateFailed; |
1020 | if (SingleChar) |
1021 | StateFailed = E.bindReturnValue(State, C, Val: *EofVal); |
1022 | else |
1023 | StateFailed = E.bindNullReturnValue(State, C); |
1024 | |
1025 | // If a (non-EOF) error occurs, the resulting value of the file position |
1026 | // indicator for the stream is indeterminate. |
1027 | StreamErrorState NewES = |
1028 | E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError; |
1029 | StateFailed = E.setStreamState( |
1030 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: NewES, IsFilePositionIndeterminate: !NewES.isFEof())); |
1031 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1032 | } |
1033 | |
1034 | void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call, |
1035 | CheckerContext &C, bool IsSingleChar) const { |
1036 | // `fputc` returns the written character on success, otherwise returns EOF. |
1037 | // `fputs` returns a nonnegative value on success, otherwise returns EOF. |
1038 | |
1039 | ProgramStateRef State = C.getState(); |
1040 | StreamOperationEvaluator E(C); |
1041 | if (!E.Init(Desc, Call, C, State)) |
1042 | return; |
1043 | |
1044 | if (IsSingleChar) { |
1045 | // Generate a transition for the success state of `fputc`. |
1046 | std::optional<NonLoc> PutVal = Call.getArgSVal(Index: 0).getAs<NonLoc>(); |
1047 | if (!PutVal) |
1048 | return; |
1049 | ProgramStateRef StateNotFailed = |
1050 | State->BindExpr(E.CE, C.getLocationContext(), *PutVal); |
1051 | StateNotFailed = |
1052 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
1053 | C.addTransition(State: StateNotFailed); |
1054 | } else { |
1055 | // Generate a transition for the success state of `fputs`. |
1056 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
1057 | ProgramStateRef StateNotFailed = |
1058 | State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
1059 | StateNotFailed = |
1060 | E.assumeBinOpNN(State: StateNotFailed, Op: BO_GE, LHS: RetVal, RHS: E.getZeroVal(Call)); |
1061 | if (!StateNotFailed) |
1062 | return; |
1063 | StateNotFailed = |
1064 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
1065 | C.addTransition(State: StateNotFailed); |
1066 | } |
1067 | |
1068 | if (!PedanticMode) |
1069 | return; |
1070 | |
1071 | // Add transition for the failed state. The resulting value of the file |
1072 | // position indicator for the stream is indeterminate. |
1073 | ProgramStateRef StateFailed = E.bindReturnValue(State, C, Val: *EofVal); |
1074 | StateFailed = E.setStreamState( |
1075 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: ErrorFError, IsFilePositionIndeterminate: true)); |
1076 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1077 | } |
1078 | |
1079 | void StreamChecker::evalFprintf(const FnDescription *Desc, |
1080 | const CallEvent &Call, |
1081 | CheckerContext &C) const { |
1082 | if (Call.getNumArgs() < 2) |
1083 | return; |
1084 | |
1085 | ProgramStateRef State = C.getState(); |
1086 | StreamOperationEvaluator E(C); |
1087 | if (!E.Init(Desc, Call, C, State)) |
1088 | return; |
1089 | |
1090 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
1091 | State = State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
1092 | auto Cond = |
1093 | E.SVB |
1094 | .evalBinOp(state: State, op: BO_GE, lhs: RetVal, rhs: E.SVB.makeZeroVal(type: E.ACtx.IntTy), |
1095 | type: E.SVB.getConditionType()) |
1096 | .getAs<DefinedOrUnknownSVal>(); |
1097 | if (!Cond) |
1098 | return; |
1099 | ProgramStateRef StateNotFailed, StateFailed; |
1100 | std::tie(args&: StateNotFailed, args&: StateFailed) = State->assume(*Cond); |
1101 | |
1102 | StateNotFailed = |
1103 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
1104 | C.addTransition(State: StateNotFailed); |
1105 | |
1106 | if (!PedanticMode) |
1107 | return; |
1108 | |
1109 | // Add transition for the failed state. The resulting value of the file |
1110 | // position indicator for the stream is indeterminate. |
1111 | StateFailed = E.setStreamState( |
1112 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: ErrorFError, IsFilePositionIndeterminate: true)); |
1113 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1114 | } |
1115 | |
1116 | void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call, |
1117 | CheckerContext &C) const { |
1118 | if (Call.getNumArgs() < 2) |
1119 | return; |
1120 | |
1121 | ProgramStateRef State = C.getState(); |
1122 | StreamOperationEvaluator E(C); |
1123 | if (!E.Init(Desc, Call, C, State)) |
1124 | return; |
1125 | |
1126 | // Add the success state. |
1127 | // In this context "success" means there is not an EOF or other read error |
1128 | // before any item is matched in 'fscanf'. But there may be match failure, |
1129 | // therefore return value can be 0 or greater. |
1130 | // It is not specified what happens if some items (not all) are matched and |
1131 | // then EOF or read error happens. Now this case is handled like a "success" |
1132 | // case, and no error flags are set on the stream. This is probably not |
1133 | // accurate, and the POSIX documentation does not tell more. |
1134 | if (!E.isStreamEof()) { |
1135 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
1136 | ProgramStateRef StateNotFailed = |
1137 | State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
1138 | StateNotFailed = |
1139 | E.assumeBinOpNN(State: StateNotFailed, Op: BO_GE, LHS: RetVal, RHS: E.getZeroVal(Call)); |
1140 | if (!StateNotFailed) |
1141 | return; |
1142 | |
1143 | if (auto const *Callee = Call.getCalleeIdentifier(); |
1144 | !Callee || !Callee->getName().equals(RHS: "vfscanf" )) { |
1145 | SmallVector<unsigned int> EscArgs; |
1146 | for (auto EscArg : llvm::seq(Begin: 2u, End: Call.getNumArgs())) |
1147 | EscArgs.push_back(Elt: EscArg); |
1148 | StateNotFailed = escapeArgs(State: StateNotFailed, C, Call, EscapingArgs: EscArgs); |
1149 | } |
1150 | |
1151 | if (StateNotFailed) |
1152 | C.addTransition(State: StateNotFailed); |
1153 | } |
1154 | |
1155 | // Add transition for the failed state. |
1156 | // Error occurs if nothing is matched yet and reading the input fails. |
1157 | // Error can be EOF, or other error. At "other error" FERROR or 'errno' can |
1158 | // be set but it is not further specified if all are required to be set. |
1159 | // Documentation does not mention, but file position will be set to |
1160 | // indeterminate similarly as at 'fread'. |
1161 | ProgramStateRef StateFailed = E.bindReturnValue(State, C, Val: *EofVal); |
1162 | StreamErrorState NewES = |
1163 | E.isStreamEof() ? ErrorFEof : ErrorNone | ErrorFEof | ErrorFError; |
1164 | StateFailed = E.setStreamState( |
1165 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: NewES, IsFilePositionIndeterminate: !NewES.isFEof())); |
1166 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1167 | } |
1168 | |
1169 | void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call, |
1170 | CheckerContext &C) const { |
1171 | ProgramStateRef State = C.getState(); |
1172 | StreamOperationEvaluator E(C); |
1173 | if (!E.Init(Desc, Call, C, State)) |
1174 | return; |
1175 | |
1176 | // Generate a transition for the success state. |
1177 | std::optional<NonLoc> PutVal = Call.getArgSVal(Index: 0).getAs<NonLoc>(); |
1178 | if (!PutVal) |
1179 | return; |
1180 | ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, Val: *PutVal); |
1181 | StateNotFailed = |
1182 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
1183 | C.addTransition(State: StateNotFailed); |
1184 | |
1185 | // Add transition for the failed state. |
1186 | // Failure of 'ungetc' does not result in feof or ferror state. |
1187 | // If the PutVal has value of EofVal the function should "fail", but this is |
1188 | // the same transition as the success state. |
1189 | // In this case only one state transition is added by the analyzer (the two |
1190 | // new states may be similar). |
1191 | ProgramStateRef StateFailed = E.bindReturnValue(State, C, Val: *EofVal); |
1192 | StateFailed = E.setStreamState(State: StateFailed, NewSS: StreamState::getOpened(L: Desc)); |
1193 | C.addTransition(State: StateFailed); |
1194 | } |
1195 | |
1196 | void StreamChecker::evalGetdelim(const FnDescription *Desc, |
1197 | const CallEvent &Call, |
1198 | CheckerContext &C) const { |
1199 | ProgramStateRef State = C.getState(); |
1200 | StreamOperationEvaluator E(C); |
1201 | if (!E.Init(Desc, Call, C, State)) |
1202 | return; |
1203 | |
1204 | // Upon successful completion, the getline() and getdelim() functions shall |
1205 | // return the number of bytes written into the buffer. |
1206 | // If the end-of-file indicator for the stream is set, the function shall |
1207 | // return -1. |
1208 | // If an error occurs, the function shall return -1 and set 'errno'. |
1209 | |
1210 | if (!E.isStreamEof()) { |
1211 | // Escape buffer and size (may change by the call). |
1212 | // May happen even at error (partial read?). |
1213 | State = escapeArgs(State, C, Call, EscapingArgs: {0, 1}); |
1214 | |
1215 | // Add transition for the successful state. |
1216 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
1217 | ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, Val: RetVal); |
1218 | StateNotFailed = |
1219 | E.assumeBinOpNN(State: StateNotFailed, Op: BO_GE, LHS: RetVal, RHS: E.getZeroVal(Call)); |
1220 | |
1221 | // On success, a buffer is allocated. |
1222 | auto NewLinePtr = getPointeeVal(PtrSVal: Call.getArgSVal(Index: 0), State); |
1223 | if (NewLinePtr && isa<DefinedOrUnknownSVal>(Val: *NewLinePtr)) |
1224 | StateNotFailed = StateNotFailed->assume( |
1225 | Cond: NewLinePtr->castAs<DefinedOrUnknownSVal>(), Assumption: true); |
1226 | |
1227 | // The buffer size `*n` must be enough to hold the whole line, and |
1228 | // greater than the return value, since it has to account for '\0'. |
1229 | SVal SizePtrSval = Call.getArgSVal(Index: 1); |
1230 | auto NVal = getPointeeVal(PtrSVal: SizePtrSval, State); |
1231 | if (NVal && isa<NonLoc>(Val: *NVal)) { |
1232 | StateNotFailed = E.assumeBinOpNN(State: StateNotFailed, Op: BO_GT, |
1233 | LHS: NVal->castAs<NonLoc>(), RHS: RetVal); |
1234 | StateNotFailed = E.bindReturnValue(State: StateNotFailed, C, Val: RetVal); |
1235 | } |
1236 | if (!StateNotFailed) |
1237 | return; |
1238 | C.addTransition(State: StateNotFailed); |
1239 | } |
1240 | |
1241 | // Add transition for the failed state. |
1242 | // If a (non-EOF) error occurs, the resulting value of the file position |
1243 | // indicator for the stream is indeterminate. |
1244 | ProgramStateRef StateFailed = E.bindReturnValue(State, C, Val: -1); |
1245 | StreamErrorState NewES = |
1246 | E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError; |
1247 | StateFailed = E.setStreamState( |
1248 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: NewES, IsFilePositionIndeterminate: !NewES.isFEof())); |
1249 | // On failure, the content of the buffer is undefined. |
1250 | if (auto NewLinePtr = getPointeeVal(PtrSVal: Call.getArgSVal(Index: 0), State)) |
1251 | StateFailed = StateFailed->bindLoc(LV: *NewLinePtr, V: UndefinedVal(), |
1252 | LCtx: C.getLocationContext()); |
1253 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1254 | } |
1255 | |
1256 | void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, |
1257 | CheckerContext &C) const { |
1258 | ProgramStateRef State = C.getState(); |
1259 | SVal StreamVal = getStreamArg(Desc, Call); |
1260 | State = ensureStreamNonNull(StreamVal, StreamE: Call.getArgExpr(Index: Desc->StreamArgNo), C, |
1261 | State); |
1262 | if (!State) |
1263 | return; |
1264 | State = ensureStreamOpened(StreamVal, C, State); |
1265 | if (!State) |
1266 | return; |
1267 | State = ensureFseekWhenceCorrect(WhenceVal: Call.getArgSVal(Index: 2), C, State); |
1268 | if (!State) |
1269 | return; |
1270 | |
1271 | C.addTransition(State); |
1272 | } |
1273 | |
1274 | void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, |
1275 | CheckerContext &C) const { |
1276 | ProgramStateRef State = C.getState(); |
1277 | StreamOperationEvaluator E(C); |
1278 | if (!E.Init(Desc, Call, C, State)) |
1279 | return; |
1280 | |
1281 | // Add success state. |
1282 | ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, Val: 0); |
1283 | // No failure: Reset the state to opened with no error. |
1284 | StateNotFailed = |
1285 | E.setStreamState(State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc)); |
1286 | C.addTransition(State: StateNotFailed); |
1287 | |
1288 | if (!PedanticMode) |
1289 | return; |
1290 | |
1291 | // Add failure state. |
1292 | // At error it is possible that fseek fails but sets none of the error flags. |
1293 | // If fseek failed, assume that the file position becomes indeterminate in any |
1294 | // case. |
1295 | // It is allowed to set the position beyond the end of the file. EOF error |
1296 | // should not occur. |
1297 | ProgramStateRef StateFailed = E.bindReturnValue(State, C, Val: -1); |
1298 | StateFailed = E.setStreamState( |
1299 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: ErrorNone | ErrorFError, IsFilePositionIndeterminate: true)); |
1300 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1301 | } |
1302 | |
1303 | void StreamChecker::evalFgetpos(const FnDescription *Desc, |
1304 | const CallEvent &Call, |
1305 | CheckerContext &C) const { |
1306 | ProgramStateRef State = C.getState(); |
1307 | StreamOperationEvaluator E(C); |
1308 | if (!E.Init(Desc, Call, C, State)) |
1309 | return; |
1310 | |
1311 | ProgramStateRef StateNotFailed, StateFailed; |
1312 | std::tie(args&: StateFailed, args&: StateNotFailed) = E.makeRetValAndAssumeDual(State, C); |
1313 | StateNotFailed = escapeArgs(State: StateNotFailed, C, Call, EscapingArgs: {1}); |
1314 | |
1315 | // This function does not affect the stream state. |
1316 | // Still we add success and failure state with the appropriate return value. |
1317 | // StdLibraryFunctionsChecker can change these states (set the 'errno' state). |
1318 | C.addTransition(State: StateNotFailed); |
1319 | C.addTransition(State: StateFailed); |
1320 | } |
1321 | |
1322 | void StreamChecker::evalFsetpos(const FnDescription *Desc, |
1323 | const CallEvent &Call, |
1324 | CheckerContext &C) const { |
1325 | ProgramStateRef State = C.getState(); |
1326 | StreamOperationEvaluator E(C); |
1327 | if (!E.Init(Desc, Call, C, State)) |
1328 | return; |
1329 | |
1330 | ProgramStateRef StateNotFailed, StateFailed; |
1331 | std::tie(args&: StateFailed, args&: StateNotFailed) = E.makeRetValAndAssumeDual(State, C); |
1332 | |
1333 | StateNotFailed = E.setStreamState( |
1334 | State: StateNotFailed, NewSS: StreamState::getOpened(L: Desc, ES: ErrorNone, IsFilePositionIndeterminate: false)); |
1335 | C.addTransition(State: StateNotFailed); |
1336 | |
1337 | if (!PedanticMode) |
1338 | return; |
1339 | |
1340 | // At failure ferror could be set. |
1341 | // The standards do not tell what happens with the file position at failure. |
1342 | // But we can assume that it is dangerous to make a next I/O operation after |
1343 | // the position was not set correctly (similar to 'fseek'). |
1344 | StateFailed = E.setStreamState( |
1345 | State: StateFailed, NewSS: StreamState::getOpened(L: Desc, ES: ErrorNone | ErrorFError, IsFilePositionIndeterminate: true)); |
1346 | |
1347 | C.addTransition(State: StateFailed, Tag: E.getFailureNoteTag(Ch: this, C)); |
1348 | } |
1349 | |
1350 | void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call, |
1351 | CheckerContext &C) const { |
1352 | ProgramStateRef State = C.getState(); |
1353 | StreamOperationEvaluator E(C); |
1354 | if (!E.Init(Desc, Call, C, State)) |
1355 | return; |
1356 | |
1357 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
1358 | ProgramStateRef StateNotFailed = |
1359 | State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
1360 | StateNotFailed = |
1361 | E.assumeBinOpNN(State: StateNotFailed, Op: BO_GE, LHS: RetVal, RHS: E.getZeroVal(Call)); |
1362 | if (!StateNotFailed) |
1363 | return; |
1364 | |
1365 | ProgramStateRef StateFailed = E.bindReturnValue(State, C, Val: -1); |
1366 | |
1367 | // This function does not affect the stream state. |
1368 | // Still we add success and failure state with the appropriate return value. |
1369 | // StdLibraryFunctionsChecker can change these states (set the 'errno' state). |
1370 | C.addTransition(State: StateNotFailed); |
1371 | C.addTransition(State: StateFailed); |
1372 | } |
1373 | |
1374 | void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call, |
1375 | CheckerContext &C) const { |
1376 | ProgramStateRef State = C.getState(); |
1377 | StreamOperationEvaluator E(C); |
1378 | if (!E.Init(Desc, Call, C, State)) |
1379 | return; |
1380 | |
1381 | State = |
1382 | E.setStreamState(State, NewSS: StreamState::getOpened(L: Desc, ES: ErrorNone, IsFilePositionIndeterminate: false)); |
1383 | C.addTransition(State); |
1384 | } |
1385 | |
1386 | void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call, |
1387 | CheckerContext &C) const { |
1388 | ProgramStateRef State = C.getState(); |
1389 | SVal StreamVal = getStreamArg(Desc, Call); |
1390 | std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>(); |
1391 | if (!Stream) |
1392 | return; |
1393 | |
1394 | ProgramStateRef StateNotNull, StateNull; |
1395 | std::tie(args&: StateNotNull, args&: StateNull) = |
1396 | C.getConstraintManager().assumeDual(State, Cond: *Stream); |
1397 | if (StateNotNull && !StateNull) |
1398 | ensureStreamOpened(StreamVal, C, State: StateNotNull); |
1399 | } |
1400 | |
1401 | void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call, |
1402 | CheckerContext &C) const { |
1403 | ProgramStateRef State = C.getState(); |
1404 | SVal StreamVal = getStreamArg(Desc, Call); |
1405 | std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>(); |
1406 | if (!Stream) |
1407 | return; |
1408 | |
1409 | // Skip if the stream can be both NULL and non-NULL. |
1410 | ProgramStateRef StateNotNull, StateNull; |
1411 | std::tie(args&: StateNotNull, args&: StateNull) = |
1412 | C.getConstraintManager().assumeDual(State, Cond: *Stream); |
1413 | if (StateNotNull && StateNull) |
1414 | return; |
1415 | if (StateNotNull && !StateNull) |
1416 | State = StateNotNull; |
1417 | else |
1418 | State = StateNull; |
1419 | |
1420 | const CallExpr *CE = dyn_cast_or_null<CallExpr>(Val: Call.getOriginExpr()); |
1421 | if (!CE) |
1422 | return; |
1423 | |
1424 | // `fflush` returns EOF on failure, otherwise returns 0. |
1425 | ProgramStateRef StateFailed = bindInt(Value: *EofVal, State, C, CE); |
1426 | ProgramStateRef StateNotFailed = bindInt(Value: 0, State, C, CE); |
1427 | |
1428 | // Clear error states if `fflush` returns 0, but retain their EOF flags. |
1429 | auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym, |
1430 | const StreamState *SS) { |
1431 | if (SS->ErrorState & ErrorFError) { |
1432 | StreamErrorState NewES = |
1433 | (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone; |
1434 | StreamState NewSS = StreamState::getOpened(L: Desc, ES: NewES, IsFilePositionIndeterminate: false); |
1435 | StateNotFailed = StateNotFailed->set<StreamMap>(K: Sym, E: NewSS); |
1436 | } |
1437 | }; |
1438 | |
1439 | if (StateNotNull && !StateNull) { |
1440 | // Skip if the input stream's state is unknown, open-failed or closed. |
1441 | if (SymbolRef StreamSym = StreamVal.getAsSymbol()) { |
1442 | const StreamState *SS = State->get<StreamMap>(key: StreamSym); |
1443 | if (SS) { |
1444 | assert(SS->isOpened() && "Stream is expected to be opened" ); |
1445 | ClearErrorInNotFailed(StreamSym, SS); |
1446 | } else |
1447 | return; |
1448 | } |
1449 | } else { |
1450 | // Clear error states for all streams. |
1451 | const StreamMapTy &Map = StateNotFailed->get<StreamMap>(); |
1452 | for (const auto &I : Map) { |
1453 | SymbolRef Sym = I.first; |
1454 | const StreamState &SS = I.second; |
1455 | if (SS.isOpened()) |
1456 | ClearErrorInNotFailed(Sym, &SS); |
1457 | } |
1458 | } |
1459 | |
1460 | C.addTransition(State: StateNotFailed); |
1461 | C.addTransition(State: StateFailed); |
1462 | } |
1463 | |
1464 | void StreamChecker::evalClearerr(const FnDescription *Desc, |
1465 | const CallEvent &Call, |
1466 | CheckerContext &C) const { |
1467 | ProgramStateRef State = C.getState(); |
1468 | StreamOperationEvaluator E(C); |
1469 | if (!E.Init(Desc, Call, C, State)) |
1470 | return; |
1471 | |
1472 | // FilePositionIndeterminate is not cleared. |
1473 | State = E.setStreamState( |
1474 | State, |
1475 | NewSS: StreamState::getOpened(L: Desc, ES: ErrorNone, IsFilePositionIndeterminate: E.SS->FilePositionIndeterminate)); |
1476 | C.addTransition(State); |
1477 | } |
1478 | |
1479 | void StreamChecker::evalFeofFerror(const FnDescription *Desc, |
1480 | const CallEvent &Call, CheckerContext &C, |
1481 | const StreamErrorState &ErrorKind) const { |
1482 | ProgramStateRef State = C.getState(); |
1483 | StreamOperationEvaluator E(C); |
1484 | if (!E.Init(Desc, Call, C, State)) |
1485 | return; |
1486 | |
1487 | if (E.SS->ErrorState & ErrorKind) { |
1488 | // Execution path with error of ErrorKind. |
1489 | // Function returns true. |
1490 | // From now on it is the only one error state. |
1491 | ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE: E.CE); |
1492 | C.addTransition(State: E.setStreamState( |
1493 | State: TrueState, NewSS: StreamState::getOpened(L: Desc, ES: ErrorKind, |
1494 | IsFilePositionIndeterminate: E.SS->FilePositionIndeterminate && |
1495 | !ErrorKind.isFEof()))); |
1496 | } |
1497 | if (StreamErrorState NewES = E.SS->ErrorState & (~ErrorKind)) { |
1498 | // Execution path(s) with ErrorKind not set. |
1499 | // Function returns false. |
1500 | // New error state is everything before minus ErrorKind. |
1501 | ProgramStateRef FalseState = E.bindReturnValue(State, C, Val: 0); |
1502 | C.addTransition(State: E.setStreamState( |
1503 | State: FalseState, |
1504 | NewSS: StreamState::getOpened( |
1505 | L: Desc, ES: NewES, IsFilePositionIndeterminate: E.SS->FilePositionIndeterminate && !NewES.isFEof()))); |
1506 | } |
1507 | } |
1508 | |
1509 | void StreamChecker::evalFileno(const FnDescription *Desc, const CallEvent &Call, |
1510 | CheckerContext &C) const { |
1511 | // Fileno should fail only if the passed pointer is invalid. |
1512 | // Some of the preconditions are checked already in preDefault. |
1513 | // Here we can assume that the operation does not fail, because if we |
1514 | // introduced a separate branch where fileno() returns -1, then it would cause |
1515 | // many unexpected and unwanted warnings in situations where fileno() is |
1516 | // called on valid streams. |
1517 | // The stream error states are not modified by 'fileno', and 'errno' is also |
1518 | // left unchanged (so this evalCall does not invalidate it, but we have a |
1519 | // custom evalCall instead of the default that would invalidate it). |
1520 | ProgramStateRef State = C.getState(); |
1521 | StreamOperationEvaluator E(C); |
1522 | if (!E.Init(Desc, Call, C, State)) |
1523 | return; |
1524 | |
1525 | NonLoc RetVal = makeRetVal(C, CE: E.CE).castAs<NonLoc>(); |
1526 | State = State->BindExpr(E.CE, C.getLocationContext(), RetVal); |
1527 | State = E.assumeBinOpNN(State, Op: BO_GE, LHS: RetVal, RHS: E.getZeroVal(Call)); |
1528 | if (!State) |
1529 | return; |
1530 | |
1531 | C.addTransition(State); |
1532 | } |
1533 | |
1534 | void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, |
1535 | CheckerContext &C) const { |
1536 | ProgramStateRef State = C.getState(); |
1537 | SVal StreamVal = getStreamArg(Desc, Call); |
1538 | State = ensureStreamNonNull(StreamVal, StreamE: Call.getArgExpr(Index: Desc->StreamArgNo), C, |
1539 | State); |
1540 | if (!State) |
1541 | return; |
1542 | State = ensureStreamOpened(StreamVal, C, State); |
1543 | if (!State) |
1544 | return; |
1545 | |
1546 | C.addTransition(State); |
1547 | } |
1548 | |
1549 | void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, |
1550 | const CallEvent &Call, CheckerContext &C, |
1551 | const StreamErrorState &ErrorKind, |
1552 | bool Indeterminate) const { |
1553 | ProgramStateRef State = C.getState(); |
1554 | SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); |
1555 | assert(StreamSym && "Operation not permitted on non-symbolic stream value." ); |
1556 | const StreamState *SS = State->get<StreamMap>(key: StreamSym); |
1557 | assert(SS && "Stream should be tracked by the checker." ); |
1558 | State = State->set<StreamMap>( |
1559 | K: StreamSym, |
1560 | E: StreamState::getOpened(L: SS->LastOperation, ES: ErrorKind, IsFilePositionIndeterminate: Indeterminate)); |
1561 | C.addTransition(State); |
1562 | } |
1563 | |
1564 | ProgramStateRef |
1565 | StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, |
1566 | CheckerContext &C, |
1567 | ProgramStateRef State) const { |
1568 | auto Stream = StreamVal.getAs<DefinedSVal>(); |
1569 | if (!Stream) |
1570 | return State; |
1571 | |
1572 | ConstraintManager &CM = C.getConstraintManager(); |
1573 | |
1574 | ProgramStateRef StateNotNull, StateNull; |
1575 | std::tie(args&: StateNotNull, args&: StateNull) = CM.assumeDual(State, Cond: *Stream); |
1576 | |
1577 | if (!StateNotNull && StateNull) { |
1578 | if (ExplodedNode *N = C.generateErrorNode(State: StateNull)) { |
1579 | auto R = std::make_unique<PathSensitiveBugReport>( |
1580 | BT_FileNull, "Stream pointer might be NULL." , N); |
1581 | if (StreamE) |
1582 | bugreporter::trackExpressionValue(N, E: StreamE, R&: *R); |
1583 | C.emitReport(R: std::move(R)); |
1584 | } |
1585 | return nullptr; |
1586 | } |
1587 | |
1588 | return StateNotNull; |
1589 | } |
1590 | |
1591 | ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, |
1592 | CheckerContext &C, |
1593 | ProgramStateRef State) const { |
1594 | SymbolRef Sym = StreamVal.getAsSymbol(); |
1595 | if (!Sym) |
1596 | return State; |
1597 | |
1598 | const StreamState *SS = State->get<StreamMap>(key: Sym); |
1599 | if (!SS) |
1600 | return State; |
1601 | |
1602 | if (SS->isClosed()) { |
1603 | // Using a stream pointer after 'fclose' causes undefined behavior |
1604 | // according to cppreference.com . |
1605 | ExplodedNode *N = C.generateErrorNode(); |
1606 | if (N) { |
1607 | C.emitReport(std::make_unique<PathSensitiveBugReport>( |
1608 | BT_UseAfterClose, |
1609 | "Stream might be already closed. Causes undefined behaviour." , N)); |
1610 | return nullptr; |
1611 | } |
1612 | |
1613 | return State; |
1614 | } |
1615 | |
1616 | if (SS->isOpenFailed()) { |
1617 | // Using a stream that has failed to open is likely to cause problems. |
1618 | // This should usually not occur because stream pointer is NULL. |
1619 | // But freopen can cause a state when stream pointer remains non-null but |
1620 | // failed to open. |
1621 | ExplodedNode *N = C.generateErrorNode(); |
1622 | if (N) { |
1623 | C.emitReport(std::make_unique<PathSensitiveBugReport>( |
1624 | BT_UseAfterOpenFailed, |
1625 | "Stream might be invalid after " |
1626 | "(re-)opening it has failed. " |
1627 | "Can cause undefined behaviour." , |
1628 | N)); |
1629 | return nullptr; |
1630 | } |
1631 | } |
1632 | |
1633 | return State; |
1634 | } |
1635 | |
1636 | ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( |
1637 | SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { |
1638 | static const char *BugMessage = |
1639 | "File position of the stream might be 'indeterminate' " |
1640 | "after a failed operation. " |
1641 | "Can cause undefined behavior." ; |
1642 | |
1643 | SymbolRef Sym = StreamVal.getAsSymbol(); |
1644 | if (!Sym) |
1645 | return State; |
1646 | |
1647 | const StreamState *SS = State->get<StreamMap>(key: Sym); |
1648 | if (!SS) |
1649 | return State; |
1650 | |
1651 | assert(SS->isOpened() && "First ensure that stream is opened." ); |
1652 | |
1653 | if (SS->FilePositionIndeterminate) { |
1654 | if (SS->ErrorState & ErrorFEof) { |
1655 | // The error is unknown but may be FEOF. |
1656 | // Continue analysis with the FEOF error state. |
1657 | // Report warning because the other possible error states. |
1658 | ExplodedNode *N = C.generateNonFatalErrorNode(State); |
1659 | if (!N) |
1660 | return nullptr; |
1661 | |
1662 | auto R = std::make_unique<PathSensitiveBugReport>( |
1663 | BT_IndeterminatePosition, BugMessage, N); |
1664 | R->markInteresting(Sym); |
1665 | C.emitReport(R: std::move(R)); |
1666 | return State->set<StreamMap>( |
1667 | K: Sym, E: StreamState::getOpened(L: SS->LastOperation, ES: ErrorFEof, IsFilePositionIndeterminate: false)); |
1668 | } |
1669 | |
1670 | // Known or unknown error state without FEOF possible. |
1671 | // Stop analysis, report error. |
1672 | if (ExplodedNode *N = C.generateErrorNode(State)) { |
1673 | auto R = std::make_unique<PathSensitiveBugReport>( |
1674 | BT_IndeterminatePosition, BugMessage, N); |
1675 | R->markInteresting(Sym); |
1676 | C.emitReport(R: std::move(R)); |
1677 | } |
1678 | |
1679 | return nullptr; |
1680 | } |
1681 | |
1682 | return State; |
1683 | } |
1684 | |
1685 | ProgramStateRef |
1686 | StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, |
1687 | ProgramStateRef State) const { |
1688 | std::optional<nonloc::ConcreteInt> CI = |
1689 | WhenceVal.getAs<nonloc::ConcreteInt>(); |
1690 | if (!CI) |
1691 | return State; |
1692 | |
1693 | int64_t X = CI->getValue().getSExtValue(); |
1694 | if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal) |
1695 | return State; |
1696 | |
1697 | if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { |
1698 | C.emitReport(std::make_unique<PathSensitiveBugReport>( |
1699 | BT_IllegalWhence, |
1700 | "The whence argument to fseek() should be " |
1701 | "SEEK_SET, SEEK_END, or SEEK_CUR." , |
1702 | N)); |
1703 | return nullptr; |
1704 | } |
1705 | |
1706 | return State; |
1707 | } |
1708 | |
1709 | void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, |
1710 | ProgramStateRef State) const { |
1711 | if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { |
1712 | auto R = std::make_unique<PathSensitiveBugReport>( |
1713 | BT_StreamEof, |
1714 | "Read function called when stream is in EOF state. " |
1715 | "Function has no effect." , |
1716 | N); |
1717 | R->markInteresting(StreamSym); |
1718 | C.emitReport(R: std::move(R)); |
1719 | return; |
1720 | } |
1721 | C.addTransition(State); |
1722 | } |
1723 | |
1724 | ExplodedNode * |
1725 | StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, |
1726 | CheckerContext &C, ExplodedNode *Pred) const { |
1727 | ExplodedNode *Err = C.generateNonFatalErrorNode(State: C.getState(), Pred); |
1728 | if (!Err) |
1729 | return Pred; |
1730 | |
1731 | for (SymbolRef LeakSym : LeakedSyms) { |
1732 | // Resource leaks can result in multiple warning that describe the same kind |
1733 | // of programming error: |
1734 | // void f() { |
1735 | // FILE *F = fopen("a.txt"); |
1736 | // if (rand()) // state split |
1737 | // return; // warning |
1738 | // } // warning |
1739 | // While this isn't necessarily true (leaking the same stream could result |
1740 | // from a different kinds of errors), the reduction in redundant reports |
1741 | // makes this a worthwhile heuristic. |
1742 | // FIXME: Add a checker option to turn this uniqueing feature off. |
1743 | const ExplodedNode *StreamOpenNode = getAcquisitionSite(N: Err, StreamSym: LeakSym, C); |
1744 | assert(StreamOpenNode && "Could not find place of stream opening." ); |
1745 | |
1746 | PathDiagnosticLocation LocUsedForUniqueing; |
1747 | if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics()) |
1748 | LocUsedForUniqueing = PathDiagnosticLocation::createBegin( |
1749 | S: StreamStmt, SM: C.getSourceManager(), |
1750 | LAC: StreamOpenNode->getLocationContext()); |
1751 | |
1752 | std::unique_ptr<PathSensitiveBugReport> R = |
1753 | std::make_unique<PathSensitiveBugReport>( |
1754 | BT_ResourceLeak, |
1755 | "Opened stream never closed. Potential resource leak." , Err, |
1756 | LocUsedForUniqueing, |
1757 | StreamOpenNode->getLocationContext()->getDecl()); |
1758 | R->markInteresting(sym: LeakSym); |
1759 | C.emitReport(R: std::move(R)); |
1760 | } |
1761 | |
1762 | return Err; |
1763 | } |
1764 | |
1765 | void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
1766 | CheckerContext &C) const { |
1767 | ProgramStateRef State = C.getState(); |
1768 | |
1769 | llvm::SmallVector<SymbolRef, 2> LeakedSyms; |
1770 | |
1771 | const StreamMapTy &Map = State->get<StreamMap>(); |
1772 | for (const auto &I : Map) { |
1773 | SymbolRef Sym = I.first; |
1774 | const StreamState &SS = I.second; |
1775 | if (!SymReaper.isDead(sym: Sym)) |
1776 | continue; |
1777 | if (SS.isOpened()) |
1778 | LeakedSyms.push_back(Elt: Sym); |
1779 | State = State->remove<StreamMap>(K: Sym); |
1780 | } |
1781 | |
1782 | ExplodedNode *N = C.getPredecessor(); |
1783 | if (!LeakedSyms.empty()) |
1784 | N = reportLeaks(LeakedSyms, C, Pred: N); |
1785 | |
1786 | C.addTransition(State, Pred: N); |
1787 | } |
1788 | |
1789 | ProgramStateRef StreamChecker::checkPointerEscape( |
1790 | ProgramStateRef State, const InvalidatedSymbols &Escaped, |
1791 | const CallEvent *Call, PointerEscapeKind Kind) const { |
1792 | // Check for file-handling system call that is not handled by the checker. |
1793 | // FIXME: The checker should be updated to handle all system calls that take |
1794 | // 'FILE*' argument. These are now ignored. |
1795 | if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) |
1796 | return State; |
1797 | |
1798 | for (SymbolRef Sym : Escaped) { |
1799 | // The symbol escaped. |
1800 | // From now the stream can be manipulated in unknown way to the checker, |
1801 | // it is not possible to handle it any more. |
1802 | // Optimistically, assume that the corresponding file handle will be closed |
1803 | // somewhere else. |
1804 | // Remove symbol from state so the following stream calls on this symbol are |
1805 | // not handled by the checker. |
1806 | State = State->remove<StreamMap>(K: Sym); |
1807 | } |
1808 | return State; |
1809 | } |
1810 | |
1811 | //===----------------------------------------------------------------------===// |
1812 | // Checker registration. |
1813 | //===----------------------------------------------------------------------===// |
1814 | |
1815 | void ento::registerStreamChecker(CheckerManager &Mgr) { |
1816 | auto *Checker = Mgr.registerChecker<StreamChecker>(); |
1817 | Checker->PedanticMode = |
1818 | Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "Pedantic" ); |
1819 | } |
1820 | |
1821 | bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) { |
1822 | return true; |
1823 | } |
1824 | |
1825 | void ento::registerStreamTesterChecker(CheckerManager &Mgr) { |
1826 | auto *Checker = Mgr.getChecker<StreamChecker>(); |
1827 | Checker->TestMode = true; |
1828 | } |
1829 | |
1830 | bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { |
1831 | return true; |
1832 | } |
1833 | |