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 @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" @@ -85,10 +86,10 @@ /// Full state information about a stream pointer. struct StreamState { /// The last file operation called in the stream. + /// Can be nullptr. const FnDescription *LastOperation; /// State of a stream symbol. - /// FIXME: We need maybe an "escaped" state later. enum KindTy { Opened, /// Stream is opened. Closed, /// Closed stream (an invalid stream pointer after it was closed). @@ -202,7 +203,7 @@ ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, CheckerContext &C, const CallExpr *CE) { State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeIntVal(Value, false)); + C.getSValBuilder().makeIntVal(Value, CE->getType())); return State; } @@ -250,10 +251,14 @@ std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, {{{"fseek"}, 3}, {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, - {{{"ftell"}, 1}, {&StreamChecker::preDefault, nullptr, 0}}, - {{{"rewind"}, 1}, {&StreamChecker::preDefault, nullptr, 0}}, - {{{"fgetpos"}, 2}, {&StreamChecker::preDefault, nullptr, 0}}, - {{{"fsetpos"}, 2}, {&StreamChecker::preDefault, nullptr, 0}}, + {{{"ftell"}, 1}, + {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, + {{{"rewind"}, 1}, + {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}}, + {{{"fgetpos"}, 2}, + {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}}, + {{{"fsetpos"}, 2}, + {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}}, {{{"clearerr"}, 1}, {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, {{{"feof"}, 1}, @@ -279,6 +284,8 @@ 0}}, }; + mutable Optional EofVal; + void evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -304,6 +311,18 @@ void evalFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void evalFgetpos(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFsetpos(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFtell(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalRewind(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + void preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -412,6 +431,17 @@ }); } + void initEof(CheckerContext &C) const { + if (EofVal) + return; + + if (const llvm::Optional OptInt = + tryExpandAsInteger("EOF", C.getPreprocessor())) + EofVal = *OptInt; + else + EofVal = -1; + } + /// Searches for the ExplodedNode where the file descriptor was acquired for /// StreamSym. static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, @@ -427,8 +457,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) inline void assertStreamStateOpened(const StreamState *SS) { - assert(SS->isOpened() && - "Previous create of error node for non-opened stream failed?"); + assert(SS->isOpened() && "Stream is expected to be opened"); } const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, @@ -458,6 +487,8 @@ void StreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + initEof(C); + const FnDescription *Desc = lookupFn(Call); if (!Desc || !Desc->PreFn) return; @@ -575,6 +606,10 @@ if (!SS) return; + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + assertStreamStateOpened(SS); // Close the File Descriptor. @@ -582,7 +617,16 @@ // and can not be used any more. State = State->set(Sym, StreamState::getClosed(Desc)); - C.addTransition(State); + // Return 0 on success, EOF on failure. + SValBuilder &SVB = C.getSValBuilder(); + ProgramStateRef StateSuccess = State->BindExpr( + CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy)); + ProgramStateRef StateFailure = + State->BindExpr(CE, C.getLocationContext(), + SVB.makeIntVal(*EofVal, C.getASTContext().IntTy)); + + C.addTransition(StateSuccess); + C.addTransition(StateFailure); } void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, @@ -767,6 +811,131 @@ C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); } +void StreamChecker::evalFgetpos(const FnDescription *Desc, + const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); + if (!Sym) + return; + + // Do not evaluate if stream is not found. + if (!State->get(Sym)) + return; + + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + DefinedSVal RetVal = makeRetVal(C, CE); + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateFailed, StateNotFailed) = + C.getConstraintManager().assumeDual(State, RetVal); + + // This function does not affect the stream state. + // Still we add success and failure state with the appropriate return value. + // StdLibraryFunctionsChecker can change these states (set the 'errno' state). + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalFsetpos(const FnDescription *Desc, + const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const StreamState *SS = State->get(StreamSym); + if (!SS) + return; + + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + assertStreamStateOpened(SS); + + DefinedSVal RetVal = makeRetVal(C, CE); + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateFailed, StateNotFailed) = + C.getConstraintManager().assumeDual(State, RetVal); + + StateNotFailed = StateNotFailed->set( + StreamSym, StreamState::getOpened(Desc, ErrorNone, false)); + + // At failure ferror could be set. + // The standards do not tell what happens with the file position at failure. + // But we can assume that it is dangerous to make a next I/O operation after + // the position was not set correctly (similar to 'fseek'). + StateFailed = StateFailed->set( + StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true)); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); + if (!Sym) + return; + + if (!State->get(Sym)) + return; + + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + SValBuilder &SVB = C.getSValBuilder(); + NonLoc RetVal = makeRetVal(C, CE).castAs(); + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), RetVal); + auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, + SVB.makeZeroVal(C.getASTContext().LongTy), + SVB.getConditionType()) + .getAs(); + if (!Cond) + return; + StateNotFailed = StateNotFailed->assume(*Cond, true); + if (!StateNotFailed) + return; + + ProgramStateRef StateFailed = State->BindExpr( + CE, C.getLocationContext(), SVB.makeIntVal(-1, C.getASTContext().LongTy)); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const StreamState *SS = State->get(StreamSym); + if (!SS) + return; + + auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + assertStreamStateOpened(SS); + + State = State->set(StreamSym, + StreamState::getOpened(Desc, ErrorNone, false)); + + C.addTransition(State); +} + void StreamChecker::evalClearerr(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { diff --git a/clang/test/Analysis/Inputs/system-header-simulator.h b/clang/test/Analysis/Inputs/system-header-simulator.h --- a/clang/test/Analysis/Inputs/system-header-simulator.h +++ b/clang/test/Analysis/Inputs/system-header-simulator.h @@ -42,9 +42,9 @@ fpos_t (*)(void *, fpos_t, int), int (*)(void *)); -FILE *fopen(const char *path, const char *mode); +FILE *fopen(const char *restrict path, const char *restrict mode); FILE *tmpfile(void); -FILE *freopen(const char *pathname, const char *mode, FILE *stream); +FILE *freopen(const char *restrict pathname, const char *restrict mode, FILE *restrict stream); int fclose(FILE *fp); size_t fread(void *restrict, size_t, size_t, FILE *restrict); size_t fwrite(const void *restrict, size_t, size_t, FILE *restrict); @@ -52,7 +52,7 @@ int fseek(FILE *__stream, long int __off, int __whence); long int ftell(FILE *__stream); void rewind(FILE *__stream); -int fgetpos(FILE *stream, fpos_t *pos); +int fgetpos(FILE *restrict stream, fpos_t *restrict pos); int fsetpos(FILE *stream, const fpos_t *pos); void clearerr(FILE *stream); int feof(FILE *stream); diff --git a/clang/test/Analysis/stream-errno-note.c b/clang/test/Analysis/stream-errno-note.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/stream-errno-note.c @@ -0,0 +1,128 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.unix.Stream \ +// RUN: -analyzer-checker=alpha.unix.Errno \ +// RUN: -analyzer-checker=apiModeling.StdCLibraryFunctions \ +// RUN: -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true \ +// RUN: -analyzer-output text -verify %s + +#include "Inputs/system-header-simulator.h" +#include "Inputs/errno_func.h" + +void check_fopen(void) { + FILE *F = fopen("xxx", "r"); + // expected-note@-1{{Assuming that function 'fopen' is successful, in this case the value 'errno' may be undefined after the call and should not be used}} + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + fclose(F); +} + +void check_tmpfile(void) { + FILE *F = tmpfile(); + // expected-note@-1{{Assuming that function 'tmpfile' is successful, in this case the value 'errno' may be undefined after the call and should not be used}} + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + fclose(F); +} + +void check_freopen(void) { + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + F = freopen("xxx", "w", F); + // expected-note@-1{{Assuming that function 'freopen' is successful}} + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + fclose(F); +} + +void check_fclose(void) { + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + (void)fclose(F); + // expected-note@-1{{Assuming that function 'fclose' is successful}} + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} + // expected-note@-1{{An undefined value may be read from 'errno'}} +} + +void check_fread(void) { + char Buf[10]; + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + (void)fread(Buf, 1, 10, F); + // expected-note@-1{{Assuming that function 'fread' is successful}} + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + (void)fclose(F); +} + +void check_fwrite(void) { + char Buf[] = "0123456789"; + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + int R = fwrite(Buf, 1, 10, F); + // expected-note@-1{{Assuming that function 'fwrite' is successful}} + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + (void)fclose(F); +} + +void check_fseek(void) { + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + (void)fseek(F, 11, SEEK_SET); + // expected-note@-1{{Assuming that function 'fseek' is successful}} + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + (void)fclose(F); +} + +void check_rewind_errnocheck(void) { + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + errno = 0; + rewind(F); // expected-note{{Function 'rewind' indicates failure only by setting of 'errno'}} + fclose(F); // expected-warning{{Value of 'errno' was not checked and may be overwritten by function 'fclose' [alpha.unix.Errno]}} + // expected-note@-1{{Value of 'errno' was not checked and may be overwritten by function 'fclose'}} +} + +void check_fileno(void) { + FILE *F = tmpfile(); + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + fileno(F); + // expected-note@-1{{Assuming that function 'fileno' is successful}} + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} + // expected-note@-1{{An undefined value may be read from 'errno'}} + (void)fclose(F); +} diff --git a/clang/test/Analysis/stream-errno.c b/clang/test/Analysis/stream-errno.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/stream-errno.c @@ -0,0 +1,224 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,alpha.unix.Errno,apiModeling.StdCLibraryFunctions,debug.ExprInspection \ +// RUN: -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true -verify %s + +#include "Inputs/system-header-simulator.h" +#include "Inputs/errno_func.h" + +extern void clang_analyzer_eval(int); +extern void clang_analyzer_dump(int); +extern void clang_analyzer_printState(); + +void check_fopen(void) { + FILE *F = fopen("xxx", "r"); + if (!F) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}} +} + +void check_tmpfile(void) { + FILE *F = tmpfile(); + if (!F) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}} +} + +void check_freopen(void) { + FILE *F = tmpfile(); + if (!F) + return; + F = freopen("xxx", "w", F); + if (!F) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fclose(void) { + FILE *F = tmpfile(); + if (!F) + return; + int Ret = fclose(F); + if (Ret == EOF) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fread_size0(void) { + char Buf[10]; + FILE *F = tmpfile(); + if (!F) + return; + fread(Buf, 0, 1, F); + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fread_nmemb0(void) { + char Buf[10]; + FILE *F = tmpfile(); + if (!F) + return; + fread(Buf, 1, 0, F); + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fread(void) { + char Buf[10]; + FILE *F = tmpfile(); + if (!F) + return; + + int R = fread(Buf, 1, 10, F); + if (R < 10) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + fclose(F); + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fwrite_size0(void) { + char Buf[] = "0123456789"; + FILE *F = tmpfile(); + if (!F) + return; + fwrite(Buf, 0, 1, F); + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fwrite_nmemb0(void) { + char Buf[] = "0123456789"; + FILE *F = tmpfile(); + if (!F) + return; + fwrite(Buf, 1, 0, F); + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fwrite(void) { + char Buf[] = "0123456789"; + FILE *F = tmpfile(); + if (!F) + return; + + int R = fwrite(Buf, 1, 10, F); + if (R < 10) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + fclose(F); + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_fseek(void) { + FILE *F = tmpfile(); + if (!F) + return; + int S = fseek(F, 11, SEEK_SET); + if (S != 0) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + fclose(F); + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} + +void check_no_errno_change(void) { + FILE *F = tmpfile(); + if (!F) + return; + errno = 1; + clearerr(F); + if (errno) {} // no-warning + feof(F); + if (errno) {} // no-warning + ferror(F); + if (errno) {} // no-warning + clang_analyzer_eval(errno == 1); // expected-warning{{TRUE}} + fclose(F); +} + +void check_fgetpos(void) { + FILE *F = tmpfile(); + if (!F) + return; + errno = 0; + fpos_t Pos; + int Ret = fgetpos(F, &Pos); + if (Ret) + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + else + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + fclose(F); +} + +void check_fsetpos(void) { + FILE *F = tmpfile(); + if (!F) + return; + errno = 0; + fpos_t Pos; + int Ret = fsetpos(F, &Pos); + if (Ret) + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + else + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + fclose(F); +} + +void check_ftell(void) { + FILE *F = tmpfile(); + if (!F) + return; + errno = 0; + long Ret = ftell(F); + if (Ret == -1) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + } else { + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(Ret >= 0); // expected-warning{{TRUE}} + } + if (errno) {} // no-warning + fclose(F); +} + +void check_rewind(void) { + FILE *F = tmpfile(); + if (!F) + return; + errno = 0; + rewind(F); + clang_analyzer_eval(errno == 0); + // expected-warning@-1{{FALSE}} + // expected-warning@-2{{TRUE}} + fclose(F); +} + +void check_fileno(void) { + FILE *F = tmpfile(); + if (!F) + return; + int N = fileno(F); + if (N == -1) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + if (errno) {} // no-warning + fclose(F); + return; + } + if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}} +} diff --git a/clang/test/Analysis/stream-error.c b/clang/test/Analysis/stream-error.c --- a/clang/test/Analysis/stream-error.c +++ b/clang/test/Analysis/stream-error.c @@ -7,6 +7,7 @@ #include "Inputs/system-header-simulator.h" void clang_analyzer_eval(int); +void clang_analyzer_dump(int); void clang_analyzer_warnIfReached(void); void StreamTesterChecker_make_feof_stream(FILE *); void StreamTesterChecker_make_ferror_stream(FILE *); @@ -101,10 +102,15 @@ } void freadwrite_zerosize(FILE *F) { - fwrite(0, 1, 0, F); - fwrite(0, 0, 1, F); - fread(0, 1, 0, F); - fread(0, 0, 1, F); + size_t Ret; + Ret = fwrite(0, 1, 0, F); + clang_analyzer_dump(Ret); // expected-warning {{0 }} + Ret = fwrite(0, 0, 1, F); + clang_analyzer_dump(Ret); // expected-warning {{0 }} + Ret = fread(0, 1, 0, F); + clang_analyzer_dump(Ret); // expected-warning {{0 }} + Ret = fread(0, 0, 1, F); + clang_analyzer_dump(Ret); // expected-warning {{0 }} } void freadwrite_zerosize_eofstate(FILE *F) { diff --git a/clang/test/Analysis/stream-noopen.c b/clang/test/Analysis/stream-noopen.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/stream-noopen.c @@ -0,0 +1,157 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.unix.Errno \ +// RUN: -analyzer-checker=alpha.unix.Stream \ +// RUN: -analyzer-checker=apiModeling.StdCLibraryFunctions \ +// RUN: -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true \ +// RUN: -analyzer-checker=debug.ExprInspection + +// enable only StdCLibraryFunctions checker +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.unix.Errno \ +// RUN: -analyzer-checker=apiModeling.StdCLibraryFunctions \ +// RUN: -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true \ +// RUN: -analyzer-checker=debug.ExprInspection + +#include "Inputs/system-header-simulator.h" +#include "Inputs/errno_var.h" + +void clang_analyzer_eval(int); + +const char *WBuf = "123456789"; +char RBuf[10]; + +void test_freopen(FILE *F) { + F = freopen("xxx", "w", F); + if (F) { + if (errno) {} // expected-warning{{undefined}} + } else { + clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}} + } +} + +void test_fread(FILE *F) { + size_t Ret = fread(RBuf, 1, 10, F); + if (Ret == 10) { + if (errno) {} // expected-warning{{undefined}} + } else { + clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}} + } + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void test_fwrite(FILE *F) { + size_t Ret = fwrite(WBuf, 1, 10, F); + if (Ret == 10) { + if (errno) {} // expected-warning{{undefined}} + } else { + clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}} + } + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void test_fclose(FILE *F) { + int Ret = fclose(F); + if (Ret == 0) { + if (errno) {} // expected-warning{{undefined}} + } else { + clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}} + clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}} + } + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void test_fseek(FILE *F) { + int Ret = fseek(F, SEEK_SET, 1); + if (Ret == 0) { + if (errno) {} // expected-warning{{undefined}} + } else { + clang_analyzer_eval(Ret == -1); // expected-warning {{TRUE}} + clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}} + } + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void check_fgetpos(FILE *F) { + errno = 0; + fpos_t Pos; + int Ret = fgetpos(F, &Pos); + if (Ret) + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + else + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + // expected-warning@-1{{FALSE}} + if (errno) {} // no-warning + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void check_fsetpos(FILE *F) { + errno = 0; + fpos_t Pos; + int Ret = fsetpos(F, &Pos); + if (Ret) + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + else + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + // expected-warning@-1{{FALSE}} + if (errno) {} // no-warning + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void check_ftell(FILE *F) { + errno = 0; + long Ret = ftell(F); + if (Ret == -1) { + clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}} + } else { + clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}} + // expected-warning@-1{{FALSE}} + clang_analyzer_eval(Ret >= 0); // expected-warning{{TRUE}} + } + if (errno) {} // no-warning + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void freadwrite_zerosize(FILE *F) { + fwrite(WBuf, 1, 0, F); + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} + fwrite(WBuf, 0, 1, F); + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} + fread(RBuf, 1, 0, F); + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} + fread(RBuf, 0, 1, F); + clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}} +} + +void freadwrite_zerosize_errno(FILE *F, int A) { + switch (A) { + case 1: + fwrite(WBuf, 1, 0, F); + if (errno) {} // expected-warning{{undefined}} + break; + case 2: + fwrite(WBuf, 0, 1, F); + if (errno) {} // expected-warning{{undefined}} + break; + case 3: + fread(RBuf, 1, 0, F); + if (errno) {} // expected-warning{{undefined}} + break; + case 4: + fread(RBuf, 0, 1, F); + if (errno) {} // expected-warning{{undefined}} + break; + } +}