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