1 //===-- StreamChecker.cpp -----------------------------------------*- C++ -*--//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines checkers that model and check stream handling functions.
11 //===----------------------------------------------------------------------===//
13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15 #include "clang/StaticAnalyzer/Core/Checker.h"
16 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
23 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
27 using namespace clang
;
29 using namespace std::placeholders
;
31 //===----------------------------------------------------------------------===//
32 // Definition of state data structures.
33 //===----------------------------------------------------------------------===//
39 /// State of the stream error flags.
40 /// Sometimes it is not known to the checker what error flags are set.
41 /// This is indicated by setting more than one flag to true.
42 /// This is an optimization to avoid state splits.
43 /// A stream can either be in FEOF or FERROR but not both at the same time.
44 /// Multiple flags are set to handle the corresponding states together.
45 struct StreamErrorState
{
46 /// The stream can be in state where none of the error flags set.
48 /// The stream can be in state where the EOF indicator is set.
50 /// The stream can be in state where the error indicator is set.
53 bool isNoError() const { return NoError
&& !FEof
&& !FError
; }
54 bool isFEof() const { return !NoError
&& FEof
&& !FError
; }
55 bool isFError() const { return !NoError
&& !FEof
&& FError
; }
57 bool operator==(const StreamErrorState
&ES
) const {
58 return NoError
== ES
.NoError
&& FEof
== ES
.FEof
&& FError
== ES
.FError
;
61 bool operator!=(const StreamErrorState
&ES
) const { return !(*this == ES
); }
63 StreamErrorState
operator|(const StreamErrorState
&E
) const {
64 return {NoError
|| E
.NoError
, FEof
|| E
.FEof
, FError
|| E
.FError
};
67 StreamErrorState
operator&(const StreamErrorState
&E
) const {
68 return {NoError
&& E
.NoError
, FEof
&& E
.FEof
, FError
&& E
.FError
};
71 StreamErrorState
operator~() const { return {!NoError
, !FEof
, !FError
}; }
73 /// Returns if the StreamErrorState is a valid object.
74 operator bool() const { return NoError
|| FEof
|| FError
; }
76 void Profile(llvm::FoldingSetNodeID
&ID
) const {
77 ID
.AddBoolean(NoError
);
79 ID
.AddBoolean(FError
);
83 const StreamErrorState ErrorNone
{true, false, false};
84 const StreamErrorState ErrorFEof
{false, true, false};
85 const StreamErrorState ErrorFError
{false, false, true};
87 /// Full state information about a stream pointer.
89 /// The last file operation called in the stream.
91 const FnDescription
*LastOperation
;
93 /// State of a stream symbol.
95 Opened
, /// Stream is opened.
96 Closed
, /// Closed stream (an invalid stream pointer after it was closed).
97 OpenFailed
/// The last open operation has failed.
100 /// State of the error flags.
101 /// Ignored in non-opened stream state but must be NoError.
102 StreamErrorState
const ErrorState
;
104 /// Indicate if the file has an "indeterminate file position indicator".
105 /// This can be set at a failing read or write or seek operation.
106 /// If it is set no more read or write is allowed.
107 /// This value is not dependent on the stream error flags:
108 /// The error flag may be cleared with `clearerr` but the file position
109 /// remains still indeterminate.
110 /// This value applies to all error states in ErrorState except FEOF.
111 /// An EOF+indeterminate state is the same as EOF state.
112 bool const FilePositionIndeterminate
= false;
114 StreamState(const FnDescription
*L
, KindTy S
, const StreamErrorState
&ES
,
115 bool IsFilePositionIndeterminate
)
116 : LastOperation(L
), State(S
), ErrorState(ES
),
117 FilePositionIndeterminate(IsFilePositionIndeterminate
) {
118 assert((!ES
.isFEof() || !IsFilePositionIndeterminate
) &&
119 "FilePositionIndeterminate should be false in FEof case.");
120 assert((State
== Opened
|| ErrorState
.isNoError()) &&
121 "ErrorState should be None in non-opened stream state.");
124 bool isOpened() const { return State
== Opened
; }
125 bool isClosed() const { return State
== Closed
; }
126 bool isOpenFailed() const { return State
== OpenFailed
; }
128 bool operator==(const StreamState
&X
) const {
129 // In not opened state error state should always NoError, so comparison
130 // here is no problem.
131 return LastOperation
== X
.LastOperation
&& State
== X
.State
&&
132 ErrorState
== X
.ErrorState
&&
133 FilePositionIndeterminate
== X
.FilePositionIndeterminate
;
136 static StreamState
getOpened(const FnDescription
*L
,
137 const StreamErrorState
&ES
= ErrorNone
,
138 bool IsFilePositionIndeterminate
= false) {
139 return StreamState
{L
, Opened
, ES
, IsFilePositionIndeterminate
};
141 static StreamState
getClosed(const FnDescription
*L
) {
142 return StreamState
{L
, Closed
, {}, false};
144 static StreamState
getOpenFailed(const FnDescription
*L
) {
145 return StreamState
{L
, OpenFailed
, {}, false};
148 void Profile(llvm::FoldingSetNodeID
&ID
) const {
149 ID
.AddPointer(LastOperation
);
150 ID
.AddInteger(State
);
151 ErrorState
.Profile(ID
);
152 ID
.AddBoolean(FilePositionIndeterminate
);
158 //===----------------------------------------------------------------------===//
159 // StreamChecker class and utility functions.
160 //===----------------------------------------------------------------------===//
165 using FnCheck
= std::function
<void(const StreamChecker
*, const FnDescription
*,
166 const CallEvent
&, CheckerContext
&)>;
168 using ArgNoTy
= unsigned int;
169 static const ArgNoTy ArgNone
= std::numeric_limits
<ArgNoTy
>::max();
171 struct FnDescription
{
177 /// Get the value of the stream argument out of the passed call event.
178 /// The call should contain a function that is described by Desc.
179 SVal
getStreamArg(const FnDescription
*Desc
, const CallEvent
&Call
) {
180 assert(Desc
&& Desc
->StreamArgNo
!= ArgNone
&&
181 "Try to get a non-existing stream argument.");
182 return Call
.getArgSVal(Desc
->StreamArgNo
);
185 /// Create a conjured symbol return value for a call expression.
186 DefinedSVal
makeRetVal(CheckerContext
&C
, const CallExpr
*CE
) {
187 assert(CE
&& "Expecting a call expression.");
189 const LocationContext
*LCtx
= C
.getLocationContext();
190 return C
.getSValBuilder()
191 .conjureSymbolVal(nullptr, CE
, LCtx
, C
.blockCount())
192 .castAs
<DefinedSVal
>();
195 ProgramStateRef
bindAndAssumeTrue(ProgramStateRef State
, CheckerContext
&C
,
196 const CallExpr
*CE
) {
197 DefinedSVal RetVal
= makeRetVal(C
, CE
);
198 State
= State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
199 State
= State
->assume(RetVal
, true);
200 assert(State
&& "Assumption on new value should not fail.");
204 ProgramStateRef
bindInt(uint64_t Value
, ProgramStateRef State
,
205 CheckerContext
&C
, const CallExpr
*CE
) {
206 State
= State
->BindExpr(CE
, C
.getLocationContext(),
207 C
.getSValBuilder().makeIntVal(Value
, CE
->getType()));
211 class StreamChecker
: public Checker
<check::PreCall
, eval::Call
,
212 check::DeadSymbols
, check::PointerEscape
> {
213 BugType BT_FileNull
{this, "NULL stream pointer", "Stream handling error"};
214 BugType BT_UseAfterClose
{this, "Closed stream", "Stream handling error"};
215 BugType BT_UseAfterOpenFailed
{this, "Invalid stream",
216 "Stream handling error"};
217 BugType BT_IndeterminatePosition
{this, "Invalid stream state",
218 "Stream handling error"};
219 BugType BT_IllegalWhence
{this, "Illegal whence argument",
220 "Stream handling error"};
221 BugType BT_StreamEof
{this, "Stream already in EOF", "Stream handling error"};
222 BugType BT_ResourceLeak
{this, "Resource leak", "Stream handling error",
223 /*SuppressOnSink =*/true};
226 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const;
227 bool evalCall(const CallEvent
&Call
, CheckerContext
&C
) const;
228 void checkDeadSymbols(SymbolReaper
&SymReaper
, CheckerContext
&C
) const;
229 ProgramStateRef
checkPointerEscape(ProgramStateRef State
,
230 const InvalidatedSymbols
&Escaped
,
231 const CallEvent
*Call
,
232 PointerEscapeKind Kind
) const;
234 /// If true, evaluate special testing stream functions.
235 bool TestMode
= false;
237 const BugType
*getBT_StreamEof() const { return &BT_StreamEof
; }
240 CallDescriptionMap
<FnDescription
> FnDescriptions
= {
241 {{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen
, ArgNone
}},
243 {&StreamChecker::preFreopen
, &StreamChecker::evalFreopen
, 2}},
244 {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen
, ArgNone
}},
246 {&StreamChecker::preDefault
, &StreamChecker::evalFclose
, 0}},
248 {std::bind(&StreamChecker::preFreadFwrite
, _1
, _2
, _3
, _4
, true),
249 std::bind(&StreamChecker::evalFreadFwrite
, _1
, _2
, _3
, _4
, true), 3}},
251 {std::bind(&StreamChecker::preFreadFwrite
, _1
, _2
, _3
, _4
, false),
252 std::bind(&StreamChecker::evalFreadFwrite
, _1
, _2
, _3
, _4
, false), 3}},
254 {&StreamChecker::preFseek
, &StreamChecker::evalFseek
, 0}},
256 {&StreamChecker::preDefault
, &StreamChecker::evalFtell
, 0}},
258 {&StreamChecker::preDefault
, &StreamChecker::evalRewind
, 0}},
260 {&StreamChecker::preDefault
, &StreamChecker::evalFgetpos
, 0}},
262 {&StreamChecker::preDefault
, &StreamChecker::evalFsetpos
, 0}},
264 {&StreamChecker::preDefault
, &StreamChecker::evalClearerr
, 0}},
266 {&StreamChecker::preDefault
,
267 std::bind(&StreamChecker::evalFeofFerror
, _1
, _2
, _3
, _4
, ErrorFEof
),
270 {&StreamChecker::preDefault
,
271 std::bind(&StreamChecker::evalFeofFerror
, _1
, _2
, _3
, _4
, ErrorFError
),
273 {{{"fileno"}, 1}, {&StreamChecker::preDefault
, nullptr, 0}},
276 CallDescriptionMap
<FnDescription
> FnTestDescriptions
= {
277 {{{"StreamTesterChecker_make_feof_stream"}, 1},
279 std::bind(&StreamChecker::evalSetFeofFerror
, _1
, _2
, _3
, _4
, ErrorFEof
),
281 {{{"StreamTesterChecker_make_ferror_stream"}, 1},
283 std::bind(&StreamChecker::evalSetFeofFerror
, _1
, _2
, _3
, _4
,
288 /// Expanded value of EOF, empty before initialization.
289 mutable std::optional
<int> EofVal
;
290 /// Expanded value of SEEK_SET, 0 if not found.
291 mutable int SeekSetVal
= 0;
292 /// Expanded value of SEEK_CUR, 1 if not found.
293 mutable int SeekCurVal
= 1;
294 /// Expanded value of SEEK_END, 2 if not found.
295 mutable int SeekEndVal
= 2;
297 void evalFopen(const FnDescription
*Desc
, const CallEvent
&Call
,
298 CheckerContext
&C
) const;
300 void preFreopen(const FnDescription
*Desc
, const CallEvent
&Call
,
301 CheckerContext
&C
) const;
302 void evalFreopen(const FnDescription
*Desc
, const CallEvent
&Call
,
303 CheckerContext
&C
) const;
305 void evalFclose(const FnDescription
*Desc
, const CallEvent
&Call
,
306 CheckerContext
&C
) const;
308 void preFreadFwrite(const FnDescription
*Desc
, const CallEvent
&Call
,
309 CheckerContext
&C
, bool IsFread
) const;
311 void evalFreadFwrite(const FnDescription
*Desc
, const CallEvent
&Call
,
312 CheckerContext
&C
, bool IsFread
) const;
314 void preFseek(const FnDescription
*Desc
, const CallEvent
&Call
,
315 CheckerContext
&C
) const;
316 void evalFseek(const FnDescription
*Desc
, const CallEvent
&Call
,
317 CheckerContext
&C
) const;
319 void evalFgetpos(const FnDescription
*Desc
, const CallEvent
&Call
,
320 CheckerContext
&C
) const;
322 void evalFsetpos(const FnDescription
*Desc
, const CallEvent
&Call
,
323 CheckerContext
&C
) const;
325 void evalFtell(const FnDescription
*Desc
, const CallEvent
&Call
,
326 CheckerContext
&C
) const;
328 void evalRewind(const FnDescription
*Desc
, const CallEvent
&Call
,
329 CheckerContext
&C
) const;
331 void preDefault(const FnDescription
*Desc
, const CallEvent
&Call
,
332 CheckerContext
&C
) const;
334 void evalClearerr(const FnDescription
*Desc
, const CallEvent
&Call
,
335 CheckerContext
&C
) const;
337 void evalFeofFerror(const FnDescription
*Desc
, const CallEvent
&Call
,
339 const StreamErrorState
&ErrorKind
) const;
341 void evalSetFeofFerror(const FnDescription
*Desc
, const CallEvent
&Call
,
343 const StreamErrorState
&ErrorKind
) const;
345 /// Check that the stream (in StreamVal) is not NULL.
346 /// If it can only be NULL a fatal error is emitted and nullptr returned.
347 /// Otherwise the return value is a new state where the stream is constrained
349 ProgramStateRef
ensureStreamNonNull(SVal StreamVal
, const Expr
*StreamE
,
351 ProgramStateRef State
) const;
353 /// Check that the stream is the opened state.
354 /// If the stream is known to be not opened an error is generated
355 /// and nullptr returned, otherwise the original state is returned.
356 ProgramStateRef
ensureStreamOpened(SVal StreamVal
, CheckerContext
&C
,
357 ProgramStateRef State
) const;
359 /// Check that the stream has not an invalid ("indeterminate") file position,
360 /// generate warning for it.
361 /// (EOF is not an invalid position.)
362 /// The returned state can be nullptr if a fatal error was generated.
363 /// It can return non-null state if the stream has not an invalid position or
364 /// there is execution path with non-invalid position.
366 ensureNoFilePositionIndeterminate(SVal StreamVal
, CheckerContext
&C
,
367 ProgramStateRef State
) const;
369 /// Check the legality of the 'whence' argument of 'fseek'.
370 /// Generate error and return nullptr if it is found to be illegal.
371 /// Otherwise returns the state.
372 /// (State is not changed here because the "whence" value is already known.)
373 ProgramStateRef
ensureFseekWhenceCorrect(SVal WhenceVal
, CheckerContext
&C
,
374 ProgramStateRef State
) const;
376 /// Generate warning about stream in EOF state.
377 /// There will be always a state transition into the passed State,
378 /// by the new non-fatal error node or (if failed) a normal transition,
379 /// to ensure uniform handling.
380 void reportFEofWarning(SymbolRef StreamSym
, CheckerContext
&C
,
381 ProgramStateRef State
) const;
383 /// Emit resource leak warnings for the given symbols.
384 /// Createn a non-fatal error node for these, and returns it (if any warnings
385 /// were generated). Return value is non-null.
386 ExplodedNode
*reportLeaks(const SmallVector
<SymbolRef
, 2> &LeakedSyms
,
387 CheckerContext
&C
, ExplodedNode
*Pred
) const;
389 /// Find the description data of the function called by a call event.
390 /// Returns nullptr if no function is recognized.
391 const FnDescription
*lookupFn(const CallEvent
&Call
) const {
392 // Recognize "global C functions" with only integral or pointer arguments
393 // (and matching name) as stream functions.
394 if (!Call
.isGlobalCFunction())
396 for (auto *P
: Call
.parameters()) {
397 QualType T
= P
->getType();
398 if (!T
->isIntegralOrEnumerationType() && !T
->isPointerType())
402 return FnDescriptions
.lookup(Call
);
405 /// Generate a message for BugReporterVisitor if the stored symbol is
406 /// marked as interesting by the actual bug report.
407 const NoteTag
*constructNoteTag(CheckerContext
&C
, SymbolRef StreamSym
,
408 const std::string
&Message
) const {
409 return C
.getNoteTag([this, StreamSym
,
410 Message
](PathSensitiveBugReport
&BR
) -> std::string
{
411 if (BR
.isInteresting(StreamSym
) && &BR
.getBugType() == &BT_ResourceLeak
)
417 const NoteTag
*constructSetEofNoteTag(CheckerContext
&C
,
418 SymbolRef StreamSym
) const {
419 return C
.getNoteTag([this, StreamSym
](PathSensitiveBugReport
&BR
) {
420 if (!BR
.isInteresting(StreamSym
) ||
421 &BR
.getBugType() != this->getBT_StreamEof())
424 BR
.markNotInteresting(StreamSym
);
426 return "Assuming stream reaches end-of-file here";
430 void initMacroValues(CheckerContext
&C
) const {
434 if (const std::optional
<int> OptInt
=
435 tryExpandAsInteger("EOF", C
.getPreprocessor()))
439 if (const std::optional
<int> OptInt
=
440 tryExpandAsInteger("SEEK_SET", C
.getPreprocessor()))
441 SeekSetVal
= *OptInt
;
442 if (const std::optional
<int> OptInt
=
443 tryExpandAsInteger("SEEK_END", C
.getPreprocessor()))
444 SeekEndVal
= *OptInt
;
445 if (const std::optional
<int> OptInt
=
446 tryExpandAsInteger("SEEK_CUR", C
.getPreprocessor()))
447 SeekCurVal
= *OptInt
;
450 /// Searches for the ExplodedNode where the file descriptor was acquired for
452 static const ExplodedNode
*getAcquisitionSite(const ExplodedNode
*N
,
457 } // end anonymous namespace
459 // This map holds the state of a stream.
460 // The stream is identified with a SymbolRef that is created when a stream
461 // opening function is modeled by the checker.
462 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap
, SymbolRef
, StreamState
)
464 inline void assertStreamStateOpened(const StreamState
*SS
) {
465 assert(SS
->isOpened() && "Stream is expected to be opened");
468 const ExplodedNode
*StreamChecker::getAcquisitionSite(const ExplodedNode
*N
,
471 ProgramStateRef State
= N
->getState();
472 // When bug type is resource leak, exploded node N may not have state info
473 // for leaked file descriptor, but predecessor should have it.
474 if (!State
->get
<StreamMap
>(StreamSym
))
475 N
= N
->getFirstPred();
477 const ExplodedNode
*Pred
= N
;
479 State
= N
->getState();
480 if (!State
->get
<StreamMap
>(StreamSym
))
483 N
= N
->getFirstPred();
489 //===----------------------------------------------------------------------===//
490 // Methods of StreamChecker.
491 //===----------------------------------------------------------------------===//
493 void StreamChecker::checkPreCall(const CallEvent
&Call
,
494 CheckerContext
&C
) const {
497 const FnDescription
*Desc
= lookupFn(Call
);
498 if (!Desc
|| !Desc
->PreFn
)
501 Desc
->PreFn(this, Desc
, Call
, C
);
504 bool StreamChecker::evalCall(const CallEvent
&Call
, CheckerContext
&C
) const {
505 const FnDescription
*Desc
= lookupFn(Call
);
506 if (!Desc
&& TestMode
)
507 Desc
= FnTestDescriptions
.lookup(Call
);
508 if (!Desc
|| !Desc
->EvalFn
)
511 Desc
->EvalFn(this, Desc
, Call
, C
);
513 return C
.isDifferent();
516 void StreamChecker::evalFopen(const FnDescription
*Desc
, const CallEvent
&Call
,
517 CheckerContext
&C
) const {
518 ProgramStateRef State
= C
.getState();
519 const CallExpr
*CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
523 DefinedSVal RetVal
= makeRetVal(C
, CE
);
524 SymbolRef RetSym
= RetVal
.getAsSymbol();
525 assert(RetSym
&& "RetVal must be a symbol here.");
527 State
= State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
529 // Bifurcate the state into two: one with a valid FILE* pointer, the other
531 ProgramStateRef StateNotNull
, StateNull
;
532 std::tie(StateNotNull
, StateNull
) =
533 C
.getConstraintManager().assumeDual(State
, RetVal
);
536 StateNotNull
->set
<StreamMap
>(RetSym
, StreamState::getOpened(Desc
));
538 StateNull
->set
<StreamMap
>(RetSym
, StreamState::getOpenFailed(Desc
));
540 C
.addTransition(StateNotNull
,
541 constructNoteTag(C
, RetSym
, "Stream opened here"));
542 C
.addTransition(StateNull
);
545 void StreamChecker::preFreopen(const FnDescription
*Desc
, const CallEvent
&Call
,
546 CheckerContext
&C
) const {
547 // Do not allow NULL as passed stream pointer but allow a closed stream.
548 ProgramStateRef State
= C
.getState();
549 State
= ensureStreamNonNull(getStreamArg(Desc
, Call
),
550 Call
.getArgExpr(Desc
->StreamArgNo
), C
, State
);
554 C
.addTransition(State
);
557 void StreamChecker::evalFreopen(const FnDescription
*Desc
,
558 const CallEvent
&Call
,
559 CheckerContext
&C
) const {
560 ProgramStateRef State
= C
.getState();
562 auto *CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
566 std::optional
<DefinedSVal
> StreamVal
=
567 getStreamArg(Desc
, Call
).getAs
<DefinedSVal
>();
571 SymbolRef StreamSym
= StreamVal
->getAsSymbol();
572 // Do not care about concrete values for stream ("(FILE *)0x12345"?).
573 // FIXME: Can be stdin, stdout, stderr such values?
577 // Do not handle untracked stream. It is probably escaped.
578 if (!State
->get
<StreamMap
>(StreamSym
))
581 // Generate state for non-failed case.
582 // Return value is the passed stream pointer.
583 // According to the documentations, the stream is closed first
584 // but any close error is ignored. The state changes to (or remains) opened.
585 ProgramStateRef StateRetNotNull
=
586 State
->BindExpr(CE
, C
.getLocationContext(), *StreamVal
);
587 // Generate state for NULL return value.
588 // Stream switches to OpenFailed state.
589 ProgramStateRef StateRetNull
=
590 State
->BindExpr(CE
, C
.getLocationContext(),
591 C
.getSValBuilder().makeNullWithType(CE
->getType()));
594 StateRetNotNull
->set
<StreamMap
>(StreamSym
, StreamState::getOpened(Desc
));
596 StateRetNull
->set
<StreamMap
>(StreamSym
, StreamState::getOpenFailed(Desc
));
598 C
.addTransition(StateRetNotNull
,
599 constructNoteTag(C
, StreamSym
, "Stream reopened here"));
600 C
.addTransition(StateRetNull
);
603 void StreamChecker::evalFclose(const FnDescription
*Desc
, const CallEvent
&Call
,
604 CheckerContext
&C
) const {
605 ProgramStateRef State
= C
.getState();
606 SymbolRef Sym
= getStreamArg(Desc
, Call
).getAsSymbol();
610 const StreamState
*SS
= State
->get
<StreamMap
>(Sym
);
614 auto *CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
618 assertStreamStateOpened(SS
);
620 // Close the File Descriptor.
621 // Regardless if the close fails or not, stream becomes "closed"
622 // and can not be used any more.
623 State
= State
->set
<StreamMap
>(Sym
, StreamState::getClosed(Desc
));
625 // Return 0 on success, EOF on failure.
626 SValBuilder
&SVB
= C
.getSValBuilder();
627 ProgramStateRef StateSuccess
= State
->BindExpr(
628 CE
, C
.getLocationContext(), SVB
.makeIntVal(0, C
.getASTContext().IntTy
));
629 ProgramStateRef StateFailure
=
630 State
->BindExpr(CE
, C
.getLocationContext(),
631 SVB
.makeIntVal(*EofVal
, C
.getASTContext().IntTy
));
633 C
.addTransition(StateSuccess
);
634 C
.addTransition(StateFailure
);
637 void StreamChecker::preFreadFwrite(const FnDescription
*Desc
,
638 const CallEvent
&Call
, CheckerContext
&C
,
639 bool IsFread
) const {
640 ProgramStateRef State
= C
.getState();
641 SVal StreamVal
= getStreamArg(Desc
, Call
);
642 State
= ensureStreamNonNull(StreamVal
, Call
.getArgExpr(Desc
->StreamArgNo
), C
,
646 State
= ensureStreamOpened(StreamVal
, C
, State
);
649 State
= ensureNoFilePositionIndeterminate(StreamVal
, C
, State
);
654 C
.addTransition(State
);
658 SymbolRef Sym
= StreamVal
.getAsSymbol();
659 if (Sym
&& State
->get
<StreamMap
>(Sym
)) {
660 const StreamState
*SS
= State
->get
<StreamMap
>(Sym
);
661 if (SS
->ErrorState
& ErrorFEof
)
662 reportFEofWarning(Sym
, C
, State
);
664 C
.addTransition(State
);
668 void StreamChecker::evalFreadFwrite(const FnDescription
*Desc
,
669 const CallEvent
&Call
, CheckerContext
&C
,
670 bool IsFread
) const {
671 ProgramStateRef State
= C
.getState();
672 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
676 const CallExpr
*CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
680 std::optional
<NonLoc
> SizeVal
= Call
.getArgSVal(1).getAs
<NonLoc
>();
683 std::optional
<NonLoc
> NMembVal
= Call
.getArgSVal(2).getAs
<NonLoc
>();
687 const StreamState
*OldSS
= State
->get
<StreamMap
>(StreamSym
);
691 assertStreamStateOpened(OldSS
);
693 // C'99 standard, §7.19.8.1.3, the return value of fread:
694 // The fread function returns the number of elements successfully read, which
695 // may be less than nmemb if a read error or end-of-file is encountered. If
696 // size or nmemb is zero, fread returns zero and the contents of the array and
697 // the state of the stream remain unchanged.
699 if (State
->isNull(*SizeVal
).isConstrainedTrue() ||
700 State
->isNull(*NMembVal
).isConstrainedTrue()) {
701 // This is the "size or nmemb is zero" case.
702 // Just return 0, do nothing more (not clear the error flags).
703 State
= bindInt(0, State
, C
, CE
);
704 C
.addTransition(State
);
708 // Generate a transition for the success state.
709 // If we know the state to be FEOF at fread, do not add a success state.
710 if (!IsFread
|| (OldSS
->ErrorState
!= ErrorFEof
)) {
711 ProgramStateRef StateNotFailed
=
712 State
->BindExpr(CE
, C
.getLocationContext(), *NMembVal
);
714 StateNotFailed
->set
<StreamMap
>(StreamSym
, StreamState::getOpened(Desc
));
715 C
.addTransition(StateNotFailed
);
718 // Add transition for the failed state.
719 NonLoc RetVal
= makeRetVal(C
, CE
).castAs
<NonLoc
>();
720 ProgramStateRef StateFailed
=
721 State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
724 .evalBinOpNN(State
, BO_LT
, RetVal
, *NMembVal
, C
.getASTContext().IntTy
)
725 .getAs
<DefinedOrUnknownSVal
>();
728 StateFailed
= StateFailed
->assume(*Cond
, true);
732 StreamErrorState NewES
;
735 (OldSS
->ErrorState
== ErrorFEof
) ? ErrorFEof
: ErrorFEof
| ErrorFError
;
738 // If a (non-EOF) error occurs, the resulting value of the file position
739 // indicator for the stream is indeterminate.
740 StreamState NewSS
= StreamState::getOpened(Desc
, NewES
, !NewES
.isFEof());
741 StateFailed
= StateFailed
->set
<StreamMap
>(StreamSym
, NewSS
);
742 if (IsFread
&& OldSS
->ErrorState
!= ErrorFEof
)
743 C
.addTransition(StateFailed
, constructSetEofNoteTag(C
, StreamSym
));
745 C
.addTransition(StateFailed
);
748 void StreamChecker::preFseek(const FnDescription
*Desc
, const CallEvent
&Call
,
749 CheckerContext
&C
) const {
750 ProgramStateRef State
= C
.getState();
751 SVal StreamVal
= getStreamArg(Desc
, Call
);
752 State
= ensureStreamNonNull(StreamVal
, Call
.getArgExpr(Desc
->StreamArgNo
), C
,
756 State
= ensureStreamOpened(StreamVal
, C
, State
);
759 State
= ensureFseekWhenceCorrect(Call
.getArgSVal(2), C
, State
);
763 C
.addTransition(State
);
766 void StreamChecker::evalFseek(const FnDescription
*Desc
, const CallEvent
&Call
,
767 CheckerContext
&C
) const {
768 ProgramStateRef State
= C
.getState();
769 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
773 const CallExpr
*CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
777 // Ignore the call if the stream is not tracked.
778 if (!State
->get
<StreamMap
>(StreamSym
))
781 const llvm::APSInt
*PosV
=
782 C
.getSValBuilder().getKnownValue(State
, Call
.getArgSVal(1));
783 const llvm::APSInt
*WhenceV
=
784 C
.getSValBuilder().getKnownValue(State
, Call
.getArgSVal(2));
786 DefinedSVal RetVal
= makeRetVal(C
, CE
);
788 // Make expression result.
789 State
= State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
791 // Bifurcate the state into failed and non-failed.
792 // Return zero on success, nonzero on error.
793 ProgramStateRef StateNotFailed
, StateFailed
;
794 std::tie(StateFailed
, StateNotFailed
) =
795 C
.getConstraintManager().assumeDual(State
, RetVal
);
797 // Reset the state to opened with no error.
799 StateNotFailed
->set
<StreamMap
>(StreamSym
, StreamState::getOpened(Desc
));
801 // It is possible that fseek fails but sets none of the error flags.
802 // If fseek failed, assume that the file position becomes indeterminate in any
804 StreamErrorState NewErrS
= ErrorNone
| ErrorFError
;
805 // Setting the position to start of file never produces EOF error.
806 if (!(PosV
&& *PosV
== 0 && WhenceV
&& *WhenceV
== SeekSetVal
))
807 NewErrS
= NewErrS
| ErrorFEof
;
808 StateFailed
= StateFailed
->set
<StreamMap
>(
809 StreamSym
, StreamState::getOpened(Desc
, NewErrS
, true));
811 C
.addTransition(StateNotFailed
);
812 C
.addTransition(StateFailed
, constructSetEofNoteTag(C
, StreamSym
));
815 void StreamChecker::evalFgetpos(const FnDescription
*Desc
,
816 const CallEvent
&Call
,
817 CheckerContext
&C
) const {
818 ProgramStateRef State
= C
.getState();
819 SymbolRef Sym
= getStreamArg(Desc
, Call
).getAsSymbol();
823 // Do not evaluate if stream is not found.
824 if (!State
->get
<StreamMap
>(Sym
))
827 auto *CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
831 DefinedSVal RetVal
= makeRetVal(C
, CE
);
832 State
= State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
833 ProgramStateRef StateNotFailed
, StateFailed
;
834 std::tie(StateFailed
, StateNotFailed
) =
835 C
.getConstraintManager().assumeDual(State
, RetVal
);
837 // This function does not affect the stream state.
838 // Still we add success and failure state with the appropriate return value.
839 // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
840 C
.addTransition(StateNotFailed
);
841 C
.addTransition(StateFailed
);
844 void StreamChecker::evalFsetpos(const FnDescription
*Desc
,
845 const CallEvent
&Call
,
846 CheckerContext
&C
) const {
847 ProgramStateRef State
= C
.getState();
848 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
852 const StreamState
*SS
= State
->get
<StreamMap
>(StreamSym
);
856 auto *CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
860 assertStreamStateOpened(SS
);
862 DefinedSVal RetVal
= makeRetVal(C
, CE
);
863 State
= State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
864 ProgramStateRef StateNotFailed
, StateFailed
;
865 std::tie(StateFailed
, StateNotFailed
) =
866 C
.getConstraintManager().assumeDual(State
, RetVal
);
868 StateNotFailed
= StateNotFailed
->set
<StreamMap
>(
869 StreamSym
, StreamState::getOpened(Desc
, ErrorNone
, false));
871 // At failure ferror could be set.
872 // The standards do not tell what happens with the file position at failure.
873 // But we can assume that it is dangerous to make a next I/O operation after
874 // the position was not set correctly (similar to 'fseek').
875 StateFailed
= StateFailed
->set
<StreamMap
>(
876 StreamSym
, StreamState::getOpened(Desc
, ErrorNone
| ErrorFError
, true));
878 C
.addTransition(StateNotFailed
);
879 C
.addTransition(StateFailed
);
882 void StreamChecker::evalFtell(const FnDescription
*Desc
, const CallEvent
&Call
,
883 CheckerContext
&C
) const {
884 ProgramStateRef State
= C
.getState();
885 SymbolRef Sym
= getStreamArg(Desc
, Call
).getAsSymbol();
889 if (!State
->get
<StreamMap
>(Sym
))
892 auto *CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
896 SValBuilder
&SVB
= C
.getSValBuilder();
897 NonLoc RetVal
= makeRetVal(C
, CE
).castAs
<NonLoc
>();
898 ProgramStateRef StateNotFailed
=
899 State
->BindExpr(CE
, C
.getLocationContext(), RetVal
);
900 auto Cond
= SVB
.evalBinOp(State
, BO_GE
, RetVal
,
901 SVB
.makeZeroVal(C
.getASTContext().LongTy
),
902 SVB
.getConditionType())
903 .getAs
<DefinedOrUnknownSVal
>();
906 StateNotFailed
= StateNotFailed
->assume(*Cond
, true);
910 ProgramStateRef StateFailed
= State
->BindExpr(
911 CE
, C
.getLocationContext(), SVB
.makeIntVal(-1, C
.getASTContext().LongTy
));
913 C
.addTransition(StateNotFailed
);
914 C
.addTransition(StateFailed
);
917 void StreamChecker::evalRewind(const FnDescription
*Desc
, const CallEvent
&Call
,
918 CheckerContext
&C
) const {
919 ProgramStateRef State
= C
.getState();
920 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
924 const StreamState
*SS
= State
->get
<StreamMap
>(StreamSym
);
928 auto *CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
932 assertStreamStateOpened(SS
);
934 State
= State
->set
<StreamMap
>(StreamSym
,
935 StreamState::getOpened(Desc
, ErrorNone
, false));
937 C
.addTransition(State
);
940 void StreamChecker::evalClearerr(const FnDescription
*Desc
,
941 const CallEvent
&Call
,
942 CheckerContext
&C
) const {
943 ProgramStateRef State
= C
.getState();
944 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
948 const StreamState
*SS
= State
->get
<StreamMap
>(StreamSym
);
952 assertStreamStateOpened(SS
);
954 // FilePositionIndeterminate is not cleared.
955 State
= State
->set
<StreamMap
>(
957 StreamState::getOpened(Desc
, ErrorNone
, SS
->FilePositionIndeterminate
));
958 C
.addTransition(State
);
961 void StreamChecker::evalFeofFerror(const FnDescription
*Desc
,
962 const CallEvent
&Call
, CheckerContext
&C
,
963 const StreamErrorState
&ErrorKind
) const {
964 ProgramStateRef State
= C
.getState();
965 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
969 const CallExpr
*CE
= dyn_cast_or_null
<CallExpr
>(Call
.getOriginExpr());
973 const StreamState
*SS
= State
->get
<StreamMap
>(StreamSym
);
977 assertStreamStateOpened(SS
);
979 if (SS
->ErrorState
& ErrorKind
) {
980 // Execution path with error of ErrorKind.
981 // Function returns true.
982 // From now on it is the only one error state.
983 ProgramStateRef TrueState
= bindAndAssumeTrue(State
, C
, CE
);
984 C
.addTransition(TrueState
->set
<StreamMap
>(
985 StreamSym
, StreamState::getOpened(Desc
, ErrorKind
,
986 SS
->FilePositionIndeterminate
&&
987 !ErrorKind
.isFEof())));
989 if (StreamErrorState NewES
= SS
->ErrorState
& (~ErrorKind
)) {
990 // Execution path(s) with ErrorKind not set.
991 // Function returns false.
992 // New error state is everything before minus ErrorKind.
993 ProgramStateRef FalseState
= bindInt(0, State
, C
, CE
);
994 C
.addTransition(FalseState
->set
<StreamMap
>(
996 StreamState::getOpened(
997 Desc
, NewES
, SS
->FilePositionIndeterminate
&& !NewES
.isFEof())));
1001 void StreamChecker::preDefault(const FnDescription
*Desc
, const CallEvent
&Call
,
1002 CheckerContext
&C
) const {
1003 ProgramStateRef State
= C
.getState();
1004 SVal StreamVal
= getStreamArg(Desc
, Call
);
1005 State
= ensureStreamNonNull(StreamVal
, Call
.getArgExpr(Desc
->StreamArgNo
), C
,
1009 State
= ensureStreamOpened(StreamVal
, C
, State
);
1013 C
.addTransition(State
);
1016 void StreamChecker::evalSetFeofFerror(const FnDescription
*Desc
,
1017 const CallEvent
&Call
, CheckerContext
&C
,
1018 const StreamErrorState
&ErrorKind
) const {
1019 ProgramStateRef State
= C
.getState();
1020 SymbolRef StreamSym
= getStreamArg(Desc
, Call
).getAsSymbol();
1021 assert(StreamSym
&& "Operation not permitted on non-symbolic stream value.");
1022 const StreamState
*SS
= State
->get
<StreamMap
>(StreamSym
);
1023 assert(SS
&& "Stream should be tracked by the checker.");
1024 State
= State
->set
<StreamMap
>(
1025 StreamSym
, StreamState::getOpened(SS
->LastOperation
, ErrorKind
));
1026 C
.addTransition(State
);
1030 StreamChecker::ensureStreamNonNull(SVal StreamVal
, const Expr
*StreamE
,
1032 ProgramStateRef State
) const {
1033 auto Stream
= StreamVal
.getAs
<DefinedSVal
>();
1037 ConstraintManager
&CM
= C
.getConstraintManager();
1039 ProgramStateRef StateNotNull
, StateNull
;
1040 std::tie(StateNotNull
, StateNull
) = CM
.assumeDual(State
, *Stream
);
1042 if (!StateNotNull
&& StateNull
) {
1043 if (ExplodedNode
*N
= C
.generateErrorNode(StateNull
)) {
1044 auto R
= std::make_unique
<PathSensitiveBugReport
>(
1045 BT_FileNull
, "Stream pointer might be NULL.", N
);
1047 bugreporter::trackExpressionValue(N
, StreamE
, *R
);
1048 C
.emitReport(std::move(R
));
1053 return StateNotNull
;
1056 ProgramStateRef
StreamChecker::ensureStreamOpened(SVal StreamVal
,
1058 ProgramStateRef State
) const {
1059 SymbolRef Sym
= StreamVal
.getAsSymbol();
1063 const StreamState
*SS
= State
->get
<StreamMap
>(Sym
);
1067 if (SS
->isClosed()) {
1068 // Using a stream pointer after 'fclose' causes undefined behavior
1069 // according to cppreference.com .
1070 ExplodedNode
*N
= C
.generateErrorNode();
1072 C
.emitReport(std::make_unique
<PathSensitiveBugReport
>(
1074 "Stream might be already closed. Causes undefined behaviour.", N
));
1081 if (SS
->isOpenFailed()) {
1082 // Using a stream that has failed to open is likely to cause problems.
1083 // This should usually not occur because stream pointer is NULL.
1084 // But freopen can cause a state when stream pointer remains non-null but
1086 ExplodedNode
*N
= C
.generateErrorNode();
1088 C
.emitReport(std::make_unique
<PathSensitiveBugReport
>(
1089 BT_UseAfterOpenFailed
,
1090 "Stream might be invalid after "
1091 "(re-)opening it has failed. "
1092 "Can cause undefined behaviour.",
1102 ProgramStateRef
StreamChecker::ensureNoFilePositionIndeterminate(
1103 SVal StreamVal
, CheckerContext
&C
, ProgramStateRef State
) const {
1104 static const char *BugMessage
=
1105 "File position of the stream might be 'indeterminate' "
1106 "after a failed operation. "
1107 "Can cause undefined behavior.";
1109 SymbolRef Sym
= StreamVal
.getAsSymbol();
1113 const StreamState
*SS
= State
->get
<StreamMap
>(Sym
);
1117 assert(SS
->isOpened() && "First ensure that stream is opened.");
1119 if (SS
->FilePositionIndeterminate
) {
1120 if (SS
->ErrorState
& ErrorFEof
) {
1121 // The error is unknown but may be FEOF.
1122 // Continue analysis with the FEOF error state.
1123 // Report warning because the other possible error states.
1124 ExplodedNode
*N
= C
.generateNonFatalErrorNode(State
);
1128 C
.emitReport(std::make_unique
<PathSensitiveBugReport
>(
1129 BT_IndeterminatePosition
, BugMessage
, N
));
1130 return State
->set
<StreamMap
>(
1131 Sym
, StreamState::getOpened(SS
->LastOperation
, ErrorFEof
, false));
1134 // Known or unknown error state without FEOF possible.
1135 // Stop analysis, report error.
1136 ExplodedNode
*N
= C
.generateErrorNode(State
);
1138 C
.emitReport(std::make_unique
<PathSensitiveBugReport
>(
1139 BT_IndeterminatePosition
, BugMessage
, N
));
1148 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal
, CheckerContext
&C
,
1149 ProgramStateRef State
) const {
1150 std::optional
<nonloc::ConcreteInt
> CI
=
1151 WhenceVal
.getAs
<nonloc::ConcreteInt
>();
1155 int64_t X
= CI
->getValue().getSExtValue();
1156 if (X
== SeekSetVal
|| X
== SeekCurVal
|| X
== SeekEndVal
)
1159 if (ExplodedNode
*N
= C
.generateNonFatalErrorNode(State
)) {
1160 C
.emitReport(std::make_unique
<PathSensitiveBugReport
>(
1162 "The whence argument to fseek() should be "
1163 "SEEK_SET, SEEK_END, or SEEK_CUR.",
1171 void StreamChecker::reportFEofWarning(SymbolRef StreamSym
, CheckerContext
&C
,
1172 ProgramStateRef State
) const {
1173 if (ExplodedNode
*N
= C
.generateNonFatalErrorNode(State
)) {
1174 auto R
= std::make_unique
<PathSensitiveBugReport
>(
1176 "Read function called when stream is in EOF state. "
1177 "Function has no effect.",
1179 R
->markInteresting(StreamSym
);
1180 C
.emitReport(std::move(R
));
1183 C
.addTransition(State
);
1187 StreamChecker::reportLeaks(const SmallVector
<SymbolRef
, 2> &LeakedSyms
,
1188 CheckerContext
&C
, ExplodedNode
*Pred
) const {
1189 ExplodedNode
*Err
= C
.generateNonFatalErrorNode(C
.getState(), Pred
);
1193 for (SymbolRef LeakSym
: LeakedSyms
) {
1194 // Resource leaks can result in multiple warning that describe the same kind
1195 // of programming error:
1197 // FILE *F = fopen("a.txt");
1198 // if (rand()) // state split
1199 // return; // warning
1201 // While this isn't necessarily true (leaking the same stream could result
1202 // from a different kinds of errors), the reduction in redundant reports
1203 // makes this a worthwhile heuristic.
1204 // FIXME: Add a checker option to turn this uniqueing feature off.
1205 const ExplodedNode
*StreamOpenNode
= getAcquisitionSite(Err
, LeakSym
, C
);
1206 assert(StreamOpenNode
&& "Could not find place of stream opening.");
1208 PathDiagnosticLocation LocUsedForUniqueing
;
1209 if (const Stmt
*StreamStmt
= StreamOpenNode
->getStmtForDiagnostics())
1210 LocUsedForUniqueing
= PathDiagnosticLocation::createBegin(
1211 StreamStmt
, C
.getSourceManager(),
1212 StreamOpenNode
->getLocationContext());
1214 std::unique_ptr
<PathSensitiveBugReport
> R
=
1215 std::make_unique
<PathSensitiveBugReport
>(
1217 "Opened stream never closed. Potential resource leak.", Err
,
1218 LocUsedForUniqueing
,
1219 StreamOpenNode
->getLocationContext()->getDecl());
1220 R
->markInteresting(LeakSym
);
1221 C
.emitReport(std::move(R
));
1227 void StreamChecker::checkDeadSymbols(SymbolReaper
&SymReaper
,
1228 CheckerContext
&C
) const {
1229 ProgramStateRef State
= C
.getState();
1231 llvm::SmallVector
<SymbolRef
, 2> LeakedSyms
;
1233 const StreamMapTy
&Map
= State
->get
<StreamMap
>();
1234 for (const auto &I
: Map
) {
1235 SymbolRef Sym
= I
.first
;
1236 const StreamState
&SS
= I
.second
;
1237 if (!SymReaper
.isDead(Sym
))
1240 LeakedSyms
.push_back(Sym
);
1241 State
= State
->remove
<StreamMap
>(Sym
);
1244 ExplodedNode
*N
= C
.getPredecessor();
1245 if (!LeakedSyms
.empty())
1246 N
= reportLeaks(LeakedSyms
, C
, N
);
1248 C
.addTransition(State
, N
);
1251 ProgramStateRef
StreamChecker::checkPointerEscape(
1252 ProgramStateRef State
, const InvalidatedSymbols
&Escaped
,
1253 const CallEvent
*Call
, PointerEscapeKind Kind
) const {
1254 // Check for file-handling system call that is not handled by the checker.
1255 // FIXME: The checker should be updated to handle all system calls that take
1256 // 'FILE*' argument. These are now ignored.
1257 if (Kind
== PSK_DirectEscapeOnCall
&& Call
->isInSystemHeader())
1260 for (SymbolRef Sym
: Escaped
) {
1261 // The symbol escaped.
1262 // From now the stream can be manipulated in unknown way to the checker,
1263 // it is not possible to handle it any more.
1264 // Optimistically, assume that the corresponding file handle will be closed
1266 // Remove symbol from state so the following stream calls on this symbol are
1267 // not handled by the checker.
1268 State
= State
->remove
<StreamMap
>(Sym
);
1273 //===----------------------------------------------------------------------===//
1274 // Checker registration.
1275 //===----------------------------------------------------------------------===//
1277 void ento::registerStreamChecker(CheckerManager
&Mgr
) {
1278 Mgr
.registerChecker
<StreamChecker
>();
1281 bool ento::shouldRegisterStreamChecker(const CheckerManager
&Mgr
) {
1285 void ento::registerStreamTesterChecker(CheckerManager
&Mgr
) {
1286 auto *Checker
= Mgr
.getChecker
<StreamChecker
>();
1287 Checker
->TestMode
= true;
1290 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager
&Mgr
) {