diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -19,84 +19,88 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include using namespace clang; using namespace ento; +using namespace std::placeholders; namespace { struct StreamState { enum Kind { Opened, Closed, OpenFailed, Escaped } K; - const Stmt *S; - StreamState(Kind k, const Stmt *s) : K(k), S(s) {} + StreamState(Kind k) : K(k) {} bool isOpened() const { return K == Opened; } bool isClosed() const { return K == Closed; } //bool isOpenFailed() const { return K == OpenFailed; } //bool isEscaped() const { return K == Escaped; } - bool operator==(const StreamState &X) const { - return K == X.K && S == X.S; - } + bool operator==(const StreamState &X) const { return K == X.K; } - static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); } - static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); } - static StreamState getOpenFailed(const Stmt *s) { - return StreamState(OpenFailed, s); - } - static StreamState getEscaped(const Stmt *s) { - return StreamState(Escaped, s); - } + static StreamState getOpened() { return StreamState(Opened); } + static StreamState getClosed() { return StreamState(Closed); } + static StreamState getOpenFailed() { return StreamState(OpenFailed); } + static StreamState getEscaped() { return StreamState(Escaped); } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); - ID.AddPointer(S); } }; class StreamChecker : public Checker { - mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, - *II_fwrite, - *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, - *II_clearerr, *II_feof, *II_ferror, *II_fileno; mutable std::unique_ptr BT_nullfp, BT_illegalwhence, BT_doubleclose, BT_ResourceLeak; public: - StreamChecker() - : II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr), - II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr), - II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr), - II_fsetpos(nullptr), II_clearerr(nullptr), II_feof(nullptr), - II_ferror(nullptr), II_fileno(nullptr) {} - bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: - void Fopen(CheckerContext &C, const CallExpr *CE) const; - void Tmpfile(CheckerContext &C, const CallExpr *CE) const; - void Fclose(CheckerContext &C, const CallExpr *CE) const; - void Fread(CheckerContext &C, const CallExpr *CE) const; - void Fwrite(CheckerContext &C, const CallExpr *CE) const; - void Fseek(CheckerContext &C, const CallExpr *CE) const; - void Ftell(CheckerContext &C, const CallExpr *CE) const; - void Rewind(CheckerContext &C, const CallExpr *CE) const; - void Fgetpos(CheckerContext &C, const CallExpr *CE) const; - void Fsetpos(CheckerContext &C, const CallExpr *CE) const; - void Clearerr(CheckerContext &C, const CallExpr *CE) const; - void Feof(CheckerContext &C, const CallExpr *CE) const; - void Ferror(CheckerContext &C, const CallExpr *CE) const; - void Fileno(CheckerContext &C, const CallExpr *CE) const; - - void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; - - ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, - CheckerContext &C) const; - ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, - CheckerContext &C) const; + using FnCheck = std::function; + + CallDescriptionMap Callbacks = { + {{"fopen"}, &StreamChecker::evalFopen}, + {{"tmpfile"}, &StreamChecker::evalFopen}, + {{"fclose", 1}, &StreamChecker::evalFclose}, + {{"fread", 4}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, + {{"fwrite", 4}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, + {{"fseek", 3}, &StreamChecker::evalFseek}, + {{"ftell", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"rewind", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"fgetpos", 2}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"fsetpos", 2}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"clearerr", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"feof", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"ferror", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"fileno", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + }; + + void evalFopen(const CallEvent &Call, CheckerContext &C) const; + void evalFclose(const CallEvent &Call, CheckerContext &C) const; + void evalFseek(const CallEvent &Call, CheckerContext &C) const; + + void checkArgNullStream(const CallEvent &Call, CheckerContext &C, + unsigned ArgI) const; + bool checkNullStream(SVal SV, CheckerContext &C, + ProgramStateRef &State) const; + void checkFseekWhence(SVal SV, CheckerContext &C, + ProgramStateRef &State) const; + bool checkDoubleClose(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const; }; } // end anonymous namespace @@ -109,115 +113,36 @@ if (!FD || FD->getKind() != Decl::Function) return false; - const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); - if (!CE) + // Recognize "global C functions" with only integral or pointer arguments + // (and matching name) as stream functions. + if (!Call.isGlobalCFunction()) return false; - - ASTContext &Ctx = C.getASTContext(); - if (!II_fopen) - II_fopen = &Ctx.Idents.get("fopen"); - if (!II_tmpfile) - II_tmpfile = &Ctx.Idents.get("tmpfile"); - if (!II_fclose) - II_fclose = &Ctx.Idents.get("fclose"); - if (!II_fread) - II_fread = &Ctx.Idents.get("fread"); - if (!II_fwrite) - II_fwrite = &Ctx.Idents.get("fwrite"); - if (!II_fseek) - II_fseek = &Ctx.Idents.get("fseek"); - if (!II_ftell) - II_ftell = &Ctx.Idents.get("ftell"); - if (!II_rewind) - II_rewind = &Ctx.Idents.get("rewind"); - if (!II_fgetpos) - II_fgetpos = &Ctx.Idents.get("fgetpos"); - if (!II_fsetpos) - II_fsetpos = &Ctx.Idents.get("fsetpos"); - if (!II_clearerr) - II_clearerr = &Ctx.Idents.get("clearerr"); - if (!II_feof) - II_feof = &Ctx.Idents.get("feof"); - if (!II_ferror) - II_ferror = &Ctx.Idents.get("ferror"); - if (!II_fileno) - II_fileno = &Ctx.Idents.get("fileno"); - - if (FD->getIdentifier() == II_fopen) { - Fopen(C, CE); - return true; - } - if (FD->getIdentifier() == II_tmpfile) { - Tmpfile(C, CE); - return true; - } - if (FD->getIdentifier() == II_fclose) { - Fclose(C, CE); - return true; - } - if (FD->getIdentifier() == II_fread) { - Fread(C, CE); - return true; - } - if (FD->getIdentifier() == II_fwrite) { - Fwrite(C, CE); - return true; - } - if (FD->getIdentifier() == II_fseek) { - Fseek(C, CE); - return true; - } - if (FD->getIdentifier() == II_ftell) { - Ftell(C, CE); - return true; - } - if (FD->getIdentifier() == II_rewind) { - Rewind(C, CE); - return true; - } - if (FD->getIdentifier() == II_fgetpos) { - Fgetpos(C, CE); - return true; - } - if (FD->getIdentifier() == II_fsetpos) { - Fsetpos(C, CE); - return true; - } - if (FD->getIdentifier() == II_clearerr) { - Clearerr(C, CE); - return true; - } - if (FD->getIdentifier() == II_feof) { - Feof(C, CE); - return true; - } - if (FD->getIdentifier() == II_ferror) { - Ferror(C, CE); - return true; - } - if (FD->getIdentifier() == II_fileno) { - Fileno(C, CE); - return true; + for (auto P : Call.parameters()) { + QualType T = P->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + return false; } - return false; -} + const FnCheck *Callback = Callbacks.lookup(Call); + if (!Callback) + return false; -void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { - OpenFileAux(C, CE); -} + (*Callback)(this, Call, C); -void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { - OpenFileAux(C, CE); + return C.isDifferent(); } -void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()) - .castAs(); + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + DefinedSVal RetVal = + svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) + .castAs(); state = state->BindExpr(CE, C.getLocationContext(), RetVal); ConstraintManager &CM = C.getConstraintManager(); @@ -226,145 +151,110 @@ ProgramStateRef stateNotNull, stateNull; std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); - if (SymbolRef Sym = RetVal.getAsSymbol()) { - // if RetVal is not NULL, set the symbol's state to Opened. - stateNotNull = - stateNotNull->set(Sym,StreamState::getOpened(CE)); - stateNull = - stateNull->set(Sym, StreamState::getOpenFailed(CE)); - - C.addTransition(stateNotNull); - C.addTransition(stateNull); - } -} - -void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); - if (state) - C.addTransition(state); -} - -void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) - return; -} - -void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) - return; -} - -void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C))) - return; - // Check the legality of the 'whence' argument of 'fseek'. - SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); - Optional CI = Whence.getAs(); - - if (!CI) - return; - - int64_t x = CI->getValue().getSExtValue(); - if (x >= 0 && x <= 2) - return; + SymbolRef Sym = RetVal.getAsSymbol(); + assert(Sym && "RetVal must be a symbol here."); + stateNotNull = stateNotNull->set(Sym, StreamState::getOpened()); + stateNull = stateNull->set(Sym, StreamState::getOpenFailed()); - if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { - if (!BT_illegalwhence) - BT_illegalwhence.reset( - new BuiltinBug(this, "Illegal whence argument", - "The whence argument to fseek() should be " - "SEEK_SET, SEEK_END, or SEEK_CUR.")); - C.emitReport(std::make_unique( - *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); - } + C.addTransition(stateNotNull); + C.addTransition(stateNull); } -void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; +void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (checkDoubleClose(Call, C, State)) + C.addTransition(State); } -void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) +void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { + const Expr *AE2 = Call.getArgExpr(2); + if (!AE2) return; -} -void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; -} + ProgramStateRef State = C.getState(); -void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) + bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State); + // Check if error was generated. + if (C.isDifferent()) return; -} -void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; -} + // Check the legality of the 'whence' argument of 'fseek'. + checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); -void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; -} + if (!C.isDifferent() && StateChanged) + C.addTransition(State); -void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; + return; } -void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; +void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, + unsigned ArgI) const { + ProgramStateRef State = C.getState(); + if (checkNullStream(Call.getArgSVal(ArgI), C, State)) + C.addTransition(State); } -ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, - CheckerContext &C) const { +bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C, + ProgramStateRef &State) const { Optional DV = SV.getAs(); if (!DV) - return nullptr; + return false; ConstraintManager &CM = C.getConstraintManager(); - ProgramStateRef stateNotNull, stateNull; - std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); + ProgramStateRef StateNotNull, StateNull; + std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); - if (!stateNotNull && stateNull) { - if (ExplodedNode *N = C.generateErrorNode(stateNull)) { + if (!StateNotNull && StateNull) { + if (ExplodedNode *N = C.generateErrorNode(StateNull)) { if (!BT_nullfp) BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", "Stream pointer might be NULL.")); C.emitReport(std::make_unique( *BT_nullfp, BT_nullfp->getDescription(), N)); } - return nullptr; + return false; + } + + if (StateNotNull) { + State = StateNotNull; + return true; + } + + return false; +} + +void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, + ProgramStateRef &State) const { + Optional CI = SV.getAs(); + if (!CI) + return; + + int64_t X = CI->getValue().getSExtValue(); + if (X >= 0 && X <= 2) + return; + + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { + if (!BT_illegalwhence) + BT_illegalwhence.reset( + new BuiltinBug(this, "Illegal whence argument", + "The whence argument to fseek() should be " + "SEEK_SET, SEEK_END, or SEEK_CUR.")); + C.emitReport(std::make_unique( + *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); } - return stateNotNull; } -ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, - ProgramStateRef state, - CheckerContext &C) const { - SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); +bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const { + SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); if (!Sym) - return state; + return false; - const StreamState *SS = state->get(Sym); + const StreamState *SS = State->get(Sym); // If the file stream is not tracked, return. if (!SS) - return state; + return false; // Check: Double close a File Descriptor could cause undefined behaviour. // Conforming to man-pages @@ -378,19 +268,21 @@ C.emitReport(std::make_unique( *BT_doubleclose, BT_doubleclose->getDescription(), N)); } - return nullptr; + return false; } // Close the File Descriptor. - return state->set(Sym, StreamState::getClosed(CE)); + State = State->set(Sym, StreamState::getClosed()); + + return true; } void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { - ProgramStateRef state = C.getState(); + ProgramStateRef State = C.getState(); // TODO: Clean up the state. - const StreamMapTy &Map = state->get(); + const StreamMapTy &Map = State->get(); for (const auto &I: Map) { SymbolRef Sym = I.first; const StreamState &SS = I.second; @@ -399,7 +291,7 @@ ExplodedNode *N = C.generateErrorNode(); if (!N) - return; + continue; if (!BT_ResourceLeak) BT_ResourceLeak.reset( diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c --- a/clang/test/Analysis/stream.c +++ b/clang/test/Analysis/stream.c @@ -1,6 +1,7 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=alpha.unix.Stream -analyzer-store region -verify %s typedef __typeof__(sizeof(int)) size_t; +typedef __typeof__(sizeof(int)) fpos_t; typedef struct _IO_FILE FILE; #define SEEK_SET 0 /* Seek from beginning of file. */ #define SEEK_CUR 1 /* Seek from current position. */ @@ -9,36 +10,93 @@ extern FILE *tmpfile(void); extern int fclose(FILE *fp); extern size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +extern size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); extern int fseek (FILE *__stream, long int __off, int __whence); extern long int ftell (FILE *__stream); extern void rewind (FILE *__stream); +extern int fgetpos(FILE *stream, fpos_t *pos); +extern int fsetpos(FILE *stream, const fpos_t *pos); +extern void clearerr(FILE *stream); +extern int feof(FILE *stream); +extern int ferror(FILE *stream); +extern int fileno(FILE *stream); -void f1(void) { - FILE *p = fopen("foo", "r"); - char buf[1024]; - fread(buf, 1, 1, p); // expected-warning {{Stream pointer might be NULL}} - fclose(p); +void check_fread() { + FILE *fp = tmpfile(); + fread(0, 0, 0, fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); } -void f2(void) { - FILE *p = fopen("foo", "r"); - fseek(p, 1, SEEK_SET); // expected-warning {{Stream pointer might be NULL}} - fclose(p); +void check_fwrite() { + FILE *fp = tmpfile(); + fwrite(0, 0, 0, fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); } -void f3(void) { - FILE *p = fopen("foo", "r"); - ftell(p); // expected-warning {{Stream pointer might be NULL}} - fclose(p); +void check_fseek() { + FILE *fp = tmpfile(); + fseek(fp, 0, 0); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_ftell() { + FILE *fp = tmpfile(); + ftell(fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_rewind() { + FILE *fp = tmpfile(); + rewind(fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_fgetpos() { + FILE *fp = tmpfile(); + fpos_t pos; + fgetpos(fp, &pos); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_fsetpos() { + FILE *fp = tmpfile(); + fpos_t pos; + fsetpos(fp, &pos); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_clearerr() { + FILE *fp = tmpfile(); + clearerr(fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_feof() { + FILE *fp = tmpfile(); + feof(fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void check_ferror() { + FILE *fp = tmpfile(); + ferror(fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); } -void f4(void) { +void check_fileno() { + FILE *fp = tmpfile(); + fileno(fp); // expected-warning {{Stream pointer might be NULL}} + fclose(fp); +} + +void f_open(void) { FILE *p = fopen("foo", "r"); - rewind(p); // expected-warning {{Stream pointer might be NULL}} + char buf[1024]; + fread(buf, 1, 1, p); // expected-warning {{Stream pointer might be NULL}} fclose(p); } -void f5(void) { +void f_seek(void) { FILE *p = fopen("foo", "r"); if (!p) return; @@ -47,26 +105,20 @@ fclose(p); } -void f6(void) { +void f_double_close(void) { FILE *p = fopen("foo", "r"); fclose(p); fclose(p); // expected-warning {{Try to close a file Descriptor already closed. Cause undefined behaviour}} } -void f7(void) { - FILE *p = tmpfile(); - ftell(p); // expected-warning {{Stream pointer might be NULL}} - fclose(p); -} - -void f8(int c) { +void f_leak(int c) { FILE *p = fopen("foo.c", "r"); if(c) return; // expected-warning {{Opened File never closed. Potential Resource leak}} fclose(p); } -FILE *f9(void) { +FILE *f_null_checked(void) { FILE *p = fopen("foo.c", "r"); if (p) return p; // no-warning @@ -82,4 +134,3 @@ void pr8081(FILE *stream, long offset, int whence) { fseek(stream, offset, whence); } - diff --git a/clang/test/Analysis/stream.cpp b/clang/test/Analysis/stream.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/stream.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.unix.Stream -analyzer-store region -verify %s + +typedef struct _IO_FILE FILE; +extern FILE *fopen(const char *path, const char *mode); + +struct X { + int A; + int B; +}; + +void *fopen(X x, const char *mode) { + return new char[4]; +} + +void f1() { + X X1; + void *p = fopen(X1, "oo"); +} // no-warning + +void f2() { + FILE *f = fopen("file", "r"); +} // expected-warning {{Opened File never closed. Potential Resource leak}}