| 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 | |