diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h --- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h @@ -78,14 +78,6 @@ /// declaration. bool isErrno(const Decl *D); -/// Produce a textual description about how \c errno is allowed to be used -/// (in a \c ErrnoCheckState). -/// The returned string is insertable into a longer warning message in the form -/// "the value 'errno' <...>". -/// Currently only the \c errno_modeling::MustNotBeChecked state is supported, -/// others are not used by the clients. -const char *describeErrnoCheckState(ErrnoCheckState CS); - /// Create a NoteTag that displays the message if the 'errno' memory region is /// marked as interesting, and resets the interestingness. const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message); diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp --- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp @@ -28,6 +28,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FormatVariadic.h" #include using namespace clang; @@ -269,12 +270,6 @@ return false; } -const char *describeErrnoCheckState(ErrnoCheckState CS) { - assert(CS == errno_modeling::MustNotBeChecked && - "Errno description not applicable."); - return "may be undefined after the call and should not be used"; -} - const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message) { return C.getNoteTag([Message](PathSensitiveBugReport &BR) -> std::string { const MemRegion *ErrnoR = BR.getErrorNode()->getState()->get(); @@ -319,18 +314,14 @@ const NoteTag *getNoteTagForStdSuccess(CheckerContext &C, llvm::StringRef Fn) { return getErrnoNoteTag( - C, (Twine("Assuming that function '") + Twine(Fn) + - Twine("' is successful, in this case the value 'errno' ") + - Twine(describeErrnoCheckState(MustNotBeChecked))) - .str()); + C, llvm::formatv( + "'errno' may be undefined after successful call to '{0}'", Fn)); } const NoteTag *getNoteTagForStdMustBeChecked(CheckerContext &C, llvm::StringRef Fn) { return getErrnoNoteTag( - C, (Twine("Function '") + Twine(Fn) + - Twine("' indicates failure only by setting of 'errno'")) - .str()); + C, llvm::formatv("'{0}' indicates failure only by setting 'errno'", Fn)); } } // namespace errno_modeling diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -51,6 +51,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" #include #include @@ -1274,7 +1275,7 @@ // Now apply the constraints. const Summary &Summary = *FoundSummary; ProgramStateRef State = C.getState(); - const ExplodedNode *Node = C.getPredecessor(); + ExplodedNode *Node = C.getPredecessor(); // Apply case/branch specifications. for (const SummaryCase &Case : Summary.getCases()) { @@ -1288,35 +1289,59 @@ if (NewState) NewState = Case.getErrnoConstraint().apply(NewState, Call, Summary, C); - if (NewState && NewState != State) { - if (Case.getNote().empty()) { - const NoteTag *NT = nullptr; - if (const auto *D = dyn_cast_or_null(Call.getDecl())) - NT = Case.getErrnoConstraint().describe(C, D->getNameAsString()); - C.addTransition(NewState, NT); - } else { - StringRef Note = Case.getNote(); + if (!NewState) + continue; + + // It is possible that NewState == State is true. + // It can occur if another checker has applied the state before us. + // Still add these note tags, the other checker should add only its + // specialized note tags. These general note tags are handled always by + // StdLibraryFunctionsChecker. + ExplodedNode *Pred = Node; + if (!Case.getNote().empty()) { + // If there is a description for this execution branch (summary case), + // use it as a note tag. + std::string Note = + llvm::formatv(Case.getNote().data(), + cast(Call.getDecl())->getDeclName()); + if (Summary.getInvalidationKd() == EvalCallAsPure) { const NoteTag *Tag = C.getNoteTag( - // Sorry couldn't help myself. - [Node, Note]() -> std::string { - // Don't emit "Assuming..." note when we ended up - // knowing in advance which branch is taken. - return (Node->succ_size() > 1) ? Note.str() : ""; + [Node, Note](PathSensitiveBugReport &BR) -> std::string { + // Try to omit the note if we know in advance which branch is + // taken (this means, only one branch exists). + // This check is performed inside the lambda, after other + // (or this) checkers had a chance to add other successors. + // Dereferencing the saved node object is valid because it's part + // of a bug report call sequence. + // FIXME: This check is not exact. We may be here after a state + // split that was performed by another checker (and can not find + // the successors). This is why this check is only used in the + // EvalCallAsPure case. + if (Node->succ_size() > 1) + return Note; + return ""; }, /*IsPrunable=*/true); - C.addTransition(NewState, Tag); + Pred = C.addTransition(NewState, Pred, Tag); + } else { + const NoteTag *Tag = C.getNoteTag(Note, /*IsPrunable=*/true); + Pred = C.addTransition(NewState, Pred, Tag); } - } else if (NewState == State) { - // It is possible that the function was evaluated in a checker callback - // where the state constraints are already applied, then no change happens - // here to the state (if the ErrnoConstraint did not change it either). - // If the evaluated function requires a NoteTag for errno change, it is - // added here. - if (const auto *D = dyn_cast_or_null(Call.getDecl())) - if (const NoteTag *NT = - Case.getErrnoConstraint().describe(C, D->getNameAsString())) - C.addTransition(NewState, NT); + if (!Pred) + break; } + + // If we can get a note tag for the errno change, add this additionally to + // the previous. This note is only about value of 'errno' and is displayed + // if 'errno' is interesting. + if (const auto *D = dyn_cast_or_null(Call.getDecl())) + if (const NoteTag *NT = + Case.getErrnoConstraint().describe(C, D->getNameAsString())) + Pred = C.addTransition(NewState, Pred, NT); + + // Add the transition if no note tag could be added. + if (Pred == Node && NewState != State) + C.addTransition(NewState); } } @@ -1662,6 +1687,10 @@ std::optional ConstFPosTPtrTy = getPointerTy(getConstTy(FPosTTy)); std::optional FPosTPtrRestrictTy = getRestrictTy(FPosTPtrTy); + constexpr llvm::StringLiteral GenericSuccessMsg( + "Assuming that '{0}' is successful"); + constexpr llvm::StringLiteral GenericFailureMsg("Assuming that '{0}' fails"); + // We are finally ready to define specifications for all supported functions. // // Argument ranges should always cover all variants. If return value @@ -1894,14 +1923,14 @@ ArgumentCondition(2U, WithinRange, Range(1, SizeMax)), ReturnValueCondition(BO_LT, ArgNo(2)), ReturnValueCondition(WithinRange, Range(0, SizeMax))}, - ErrnoNEZeroIrrelevant) + ErrnoNEZeroIrrelevant, GenericFailureMsg) .Case({ArgumentCondition(1U, WithinRange, Range(1, SizeMax)), ReturnValueCondition(BO_EQ, ArgNo(2)), ReturnValueCondition(WithinRange, Range(0, SizeMax))}, - ErrnoMustNotBeChecked) + ErrnoMustNotBeChecked, GenericSuccessMsg) .Case({ArgumentCondition(1U, WithinRange, SingleValue(0)), ReturnValueCondition(WithinRange, SingleValue(0))}, - ErrnoMustNotBeChecked) + ErrnoMustNotBeChecked, GenericSuccessMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(3))) // FIXME: It should be allowed to have a null buffer if any of @@ -2018,17 +2047,17 @@ Signature(ArgTypes{ConstCharPtrRestrictTy, ConstCharPtrRestrictTy}, RetType{FilePtrTy}), Summary(NoEvalCall) - .Case({NotNull(Ret)}, ErrnoMustNotBeChecked) - .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); // FILE *tmpfile(void); - addToFunctionSummaryMap("tmpfile", - Signature(ArgTypes{}, RetType{FilePtrTy}), - Summary(NoEvalCall) - .Case({NotNull(Ret)}, ErrnoMustNotBeChecked) - .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant)); + addToFunctionSummaryMap( + "tmpfile", Signature(ArgTypes{}, RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg)); // FILE *freopen(const char *restrict pathname, const char *restrict mode, // FILE *restrict stream); @@ -2039,8 +2068,8 @@ RetType{FilePtrTy}), Summary(NoEvalCall) .Case({ReturnValueCondition(BO_EQ, ArgNo(2))}, - ErrnoMustNotBeChecked) - .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant) + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(1))) .ArgConstraint(NotNull(ArgNo(2)))); @@ -2048,9 +2077,9 @@ addToFunctionSummaryMap( "fclose", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZero, ErrnoMustNotBeChecked) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) .Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))}, - ErrnoNEZeroIrrelevant) + ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int fseek(FILE *stream, long offset, int whence); @@ -2060,8 +2089,8 @@ addToFunctionSummaryMap( "fseek", Signature(ArgTypes{FilePtrTy, LongTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZero, ErrnoMustNotBeChecked) - .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(ArgumentCondition(2, WithinRange, {{0, 2}}))); @@ -2074,8 +2103,8 @@ Signature(ArgTypes{FilePtrRestrictTy, FPosTPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZero, ErrnoUnchanged) - .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant) + .Case(ReturnsZero, ErrnoUnchanged, GenericSuccessMsg) + .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -2087,8 +2116,8 @@ "fsetpos", Signature(ArgTypes{FilePtrTy, ConstFPosTPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZero, ErrnoUnchanged) - .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant) + .Case(ReturnsZero, ErrnoUnchanged, GenericSuccessMsg) + .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -2100,16 +2129,17 @@ "ftell", Signature(ArgTypes{FilePtrTy}, RetType{LongTy}), Summary(NoEvalCall) .Case({ReturnValueCondition(WithinRange, Range(1, LongMax))}, - ErrnoUnchanged) - .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + ErrnoUnchanged, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int fileno(FILE *stream); addToFunctionSummaryMap( "fileno", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked) - .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // void rewind(FILE *stream); @@ -2151,8 +2181,8 @@ addToFunctionSummaryMap( "access", Signature(ArgTypes{ConstCharPtrTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZero, ErrnoMustNotBeChecked) - .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int faccessat(int dirfd, const char *pathname, int mode, int flags); diff --git a/clang/test/Analysis/errno-stdlibraryfunctions-notes.c b/clang/test/Analysis/errno-stdlibraryfunctions-notes.c --- a/clang/test/Analysis/errno-stdlibraryfunctions-notes.c +++ b/clang/test/Analysis/errno-stdlibraryfunctions-notes.c @@ -13,9 +13,11 @@ void clang_analyzer_warnIfReached(); void test1() { - access("path", 0); // no note here access("path", 0); - // expected-note@-1{{Assuming that function 'access' is successful, in this case the value 'errno' may be undefined after the call and should not be used}} + // expected-note@-1{{Assuming that 'access' fails}} + access("path", 0); + // expected-note@-1{{Assuming that 'access' is successful}} + // expected-note@-2{{'errno' may be undefined after successful call to 'access'}} if (errno != 0) { // expected-warning@-1{{An undefined value may be read from 'errno'}} // expected-note@-2{{An undefined value may be read from 'errno'}} @@ -24,7 +26,8 @@ void test2() { if (access("path", 0) == -1) { - // expected-note@-1{{Taking true branch}} + // expected-note@-1{{Assuming that 'access' fails}} + // expected-note@-2{{Taking true branch}} // Failure path. if (errno != 0) { // expected-note@-1{{'errno' is not equal to 0}} @@ -39,8 +42,9 @@ void test3() { if (access("path", 0) != -1) { // Success path. - // expected-note@-2{{Assuming that function 'access' is successful, in this case the value 'errno' may be undefined after the call and should not be used}} - // expected-note@-3{{Taking true branch}} + // expected-note@-2{{Assuming that 'access' is successful}} + // expected-note@-3{{'errno' may be undefined after successful call to 'access'}} + // expected-note@-4{{Taking true branch}} if (errno != 0) { // expected-warning@-1{{An undefined value may be read from 'errno'}} // expected-note@-2{{An undefined value may be read from 'errno'}} diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp b/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp --- a/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp @@ -52,6 +52,7 @@ int __test_case_note(); int test_case_note_1(int y) { + int x0 = __test_case_note(); // expected-note{{Function returns 1}} int x = __test_case_note(); // expected-note{{Function returns 0}} \ // expected-note{{'x' initialized here}} return y / x; // expected-warning{{Division by zero}} \ diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints.c b/clang/test/Analysis/std-c-library-functions-arg-constraints.c --- a/clang/test/Analysis/std-c-library-functions-arg-constraints.c +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints.c @@ -170,7 +170,8 @@ // bugpath-note{{The 1st argument to 'fread' is NULL but should not be NULL}} } void test_notnull_symbolic(FILE *fp, int *buf) { - fread(buf, sizeof(int), 10, fp); + fread(buf, sizeof(int), 10, fp); // \ + // bugpath-note{{'fread' fails}} clang_analyzer_eval(buf != 0); // \ // report-warning{{TRUE}} \ // bugpath-warning{{TRUE}} \ diff --git a/clang/test/Analysis/std-c-library-functions-path-notes.c b/clang/test/Analysis/std-c-library-functions-path-notes.c --- a/clang/test/Analysis/std-c-library-functions-path-notes.c +++ b/clang/test/Analysis/std-c-library-functions-path-notes.c @@ -1,7 +1,9 @@ // RUN: %clang_analyze_cc1 -verify %s \ // RUN: -analyzer-checker=core,alpha.unix.StdCLibraryFunctions \ +// RUN: -analyzer-config alpha.unix.StdCLibraryFunctions:ModelPOSIX=true \ // RUN: -analyzer-output=text +#include "Inputs/std-c-library-functions-POSIX.h" #define NULL ((void *)0) char *getenv(const char *); @@ -19,10 +21,12 @@ // expected-note {{Array access (from variable 'env') results in a null pointer dereference}} } -int test_isalpha(int *x, char c) { +int test_isalpha(int *x, char c, char d) { + int iad = isalpha(d);// \ + // expected-note{{Assuming the character is non-alphabetical}} if (isalpha(c)) {// \ - // expected-note{{Assuming the character is alphabetical}} \ - // expected-note{{Taking true branch}} + // expected-note{{Taking true branch}} \ + // expected-note{{Assuming the character is alphabetical}} x = NULL; // \ // expected-note{{Null pointer value stored to 'x'}} } @@ -34,8 +38,8 @@ int test_isdigit(int *x, char c) { if (!isdigit(c)) {// \ - // expected-note{{Assuming the character is not a digit}} \ - // expected-note{{Taking true branch}} + // expected-note{{Assuming the character is not a digit}} \ + // expected-note{{Taking true branch}} x = NULL; // \ // expected-note{{Null pointer value stored to 'x'}} } @@ -58,3 +62,18 @@ // expected-warning{{Dereference of null pointer (loaded from variable 'x')}} \ // expected-note {{Dereference of null pointer (loaded from variable 'x')}} } + +int test_bugpath_notes(FILE *f1, char c, FILE *f2) { + int f = fileno(f2); // \ + // expected-note{{Assuming that 'fileno' is successful}} + if (f == -1) // \ + // expected-note{{Taking false branch}} + return 0; + int l = islower(c); + f = fileno(f1); // \ + // expected-note{{Value assigned to 'f'}} \ + // expected-note{{Assuming that 'fileno' fails}} + return dup(f); // \ + // expected-warning{{The 1st argument to 'dup' is -1 but should be >= 0}} \ + // expected-note{{The 1st argument to 'dup' is -1 but should be >= 0}} +} diff --git a/clang/test/Analysis/stream-errno-note.c b/clang/test/Analysis/stream-errno-note.c --- a/clang/test/Analysis/stream-errno-note.c +++ b/clang/test/Analysis/stream-errno-note.c @@ -10,7 +10,8 @@ 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@-1{{'errno' may be undefined after successful call to 'fopen'}} + // expected-note@-2{{'fopen' is successful}} // expected-note@+2{{'F' is non-null}} // expected-note@+1{{Taking false branch}} if (!F) @@ -22,7 +23,8 @@ 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@-1{{'errno' may be undefined after successful call to 'tmpfile'}} + // expected-note@-2{{'tmpfile' is successful}} // expected-note@+2{{'F' is non-null}} // expected-note@+1{{Taking false branch}} if (!F) @@ -34,12 +36,14 @@ void check_freopen(void) { FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} // 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@-1{{'errno' may be undefined after successful call to 'freopen'}} + // expected-note@-2{{'freopen' is successful}} // expected-note@+2{{'F' is non-null}} // expected-note@+1{{Taking false branch}} if (!F) @@ -51,12 +55,14 @@ void check_fclose(void) { FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} // 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}} + // expected-note@-1{{'errno' may be undefined after successful call to 'fclose'}} + // expected-note@-2{{'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'}} } @@ -64,26 +70,60 @@ void check_fread(void) { char Buf[10]; FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} // 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}} + // expected-note@-1{{'errno' may be undefined after successful call to 'fread'}} + // expected-note@-2{{'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_fread_size0(void) { + char Buf[10]; + FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + fread(Buf, 0, 1, F); + // expected-note@-1{{'errno' may be undefined after successful call to 'fread'}} + // expected-note@-2{{'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 check_fread_nmemb0(void) { + char Buf[10]; + FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} + // expected-note@+2{{'F' is non-null}} + // expected-note@+1{{Taking false branch}} + if (!F) + return; + fread(Buf, 1, 0, F); + // expected-note@-1{{'errno' may be undefined after successful call to 'fread'}} + // expected-note@-2{{'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 check_fwrite(void) { char Buf[] = "0123456789"; FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} // 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}} + // expected-note@-1{{'errno' may be undefined after successful call to 'fwrite'}} + // expected-note@-2{{'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); @@ -91,12 +131,14 @@ void check_fseek(void) { FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} // 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}} + // expected-note@-1{{'errno' may be undefined after successful call to 'fseek'}} + // expected-note@-2{{'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); @@ -104,24 +146,27 @@ void check_rewind_errnocheck(void) { FILE *F = tmpfile(); + // expected-note@-1{{'tmpfile' is successful}} // 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'}} + rewind(F); // expected-note{{'rewind' indicates failure only by setting '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@-1{{'tmpfile' is successful}} // 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}} + // expected-note@-1{{Assuming that 'fileno' is successful}} + // expected-note@-2{{'errno' may be undefined after successful call to 'fileno'}} 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-note.c b/clang/test/Analysis/stream-note.c --- a/clang/test/Analysis/stream-note.c +++ b/clang/test/Analysis/stream-note.c @@ -7,11 +7,13 @@ void check_note_at_correct_open(void) { FILE *F1 = tmpfile(); // expected-note {{Stream opened here}} + // stdargs-note@-1 {{'tmpfile' is successful}} if (!F1) // expected-note@-1 {{'F1' is non-null}} // expected-note@-2 {{Taking false branch}} return; FILE *F2 = tmpfile(); + // stdargs-note@-1 {{'tmpfile' is successful}} if (!F2) { // expected-note@-1 {{'F2' is non-null}} // expected-note@-2 {{Taking false branch}} @@ -20,6 +22,7 @@ } rewind(F2); fclose(F2); + // stdargs-note@-1 {{'fclose' fails}} rewind(F1); } // expected-warning@-1 {{Opened stream never closed. Potential resource leak}} @@ -27,6 +30,7 @@ void check_note_fopen(void) { FILE *F = fopen("file", "r"); // expected-note {{Stream opened here}} + // stdargs-note@-1 {{'fopen' is successful}} if (!F) // expected-note@-1 {{'F' is non-null}} // expected-note@-2 {{Taking false branch}} @@ -37,11 +41,13 @@ void check_note_freopen(void) { FILE *F = fopen("file", "r"); // expected-note {{Stream opened here}} + // stdargs-note@-1 {{'fopen' is successful}} if (!F) // expected-note@-1 {{'F' is non-null}} // expected-note@-2 {{Taking false branch}} return; F = freopen(0, "w", F); // expected-note {{Stream reopened here}} + // stdargs-note@-1 {{'freopen' is successful}} if (!F) // expected-note@-1 {{'F' is non-null}} // expected-note@-2 {{Taking false branch}} @@ -52,6 +58,8 @@ void check_note_leak_2(int c) { FILE *F1 = fopen("foo1.c", "r"); // expected-note {{Stream opened here}} + // stdargs-note@-1 {{'fopen' is successful}} + // stdargs-note@-2 {{'fopen' is successful}} if (!F1) // expected-note@-1 {{'F1' is non-null}} // expected-note@-2 {{Taking false branch}} @@ -59,6 +67,8 @@ // expected-note@-4 {{Taking false branch}} return; FILE *F2 = fopen("foo2.c", "r"); // expected-note {{Stream opened here}} + // stdargs-note@-1 {{'fopen' is successful}} + // stdargs-note@-2 {{'fopen' is successful}} if (!F2) { // expected-note@-1 {{'F2' is non-null}} // expected-note@-2 {{Taking false branch}} @@ -84,6 +94,7 @@ void check_track_null(void) { FILE *F; F = fopen("foo1.c", "r"); // expected-note {{Value assigned to 'F'}} expected-note {{Assuming pointer value is null}} + // stdargs-note@-1 {{'fopen' fails}} if (F != NULL) { // expected-note {{Taking false branch}} expected-note {{'F' is equal to NULL}} fclose(F); return; @@ -96,13 +107,16 @@ FILE *F; char Buf[10]; F = fopen("foo1.c", "r"); + // stdargs-note@-1 {{'fopen' is successful}} if (F == NULL) { // expected-note {{Taking false branch}} expected-note {{'F' is not equal to NULL}} return; } fread(Buf, 1, 1, F); + // stdargs-note@-1 {{'fread' fails}} if (feof(F)) { // expected-note {{Taking true branch}} clearerr(F); fread(Buf, 1, 1, F); // expected-note {{Assuming stream reaches end-of-file here}} + // stdargs-note@-1 {{'fread' fails}} if (feof(F)) { // expected-note {{Taking true branch}} fread(Buf, 1, 1, F); // expected-warning {{Read function called when stream is in EOF state. Function has no effect}} // expected-note@-1 {{Read function called when stream is in EOF state. Function has no effect}} @@ -115,10 +129,12 @@ FILE *F; char Buf[10]; F = fopen("foo1.c", "r"); + // stdargs-note@-1 {{'fopen' is successful}} if (F == NULL) { // expected-note {{Taking false branch}} expected-note {{'F' is not equal to NULL}} return; } fread(Buf, 1, 1, F); + // stdargs-note@-1 {{'fread' is successful}} if (feof(F)) { // expected-note {{Taking false branch}} fclose(F); return; @@ -127,6 +143,7 @@ return; } fread(Buf, 1, 1, F); // expected-note {{Assuming stream reaches end-of-file here}} + // stdargs-note@-1 {{'fread' fails}} if (feof(F)) { // expected-note {{Taking true branch}} fread(Buf, 1, 1, F); // expected-warning {{Read function called when stream is in EOF state. Function has no effect}} // expected-note@-1 {{Read function called when stream is in EOF state. Function has no effect}} @@ -138,9 +155,11 @@ FILE *F; char Buf[10]; F = fopen("foo1.c", "r"); + // stdargs-note@-1 {{'fopen' is successful}} if (F == NULL) // expected-note {{Taking false branch}} expected-note {{'F' is not equal to NULL}} return; int RRet = fread(Buf, 1, 1, F); // expected-note {{Assuming stream reaches end-of-file here}} + // stdargs-note@-1 {{'fread' fails}} if (ferror(F)) { // expected-note {{Taking false branch}} } else { fread(Buf, 1, 1, F); // expected-warning {{Read function called when stream is in EOF state. Function has no effect}}