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