Skip to content

Commit 699e101

Browse files
authored
[clang][analyzer] Improve 'errno' handling in StdLibraryFunctionsChecker. (#71392)
The checker now displays one combined note tag for errno-related and "case"-related notes. Previous functions in the errno-modeling part that were used for construction of note tags are removed. The note tag added by StdLibraryFunctionsChecker contains the code to display the note tag for 'errno' (this was done previously by these removed functions).
1 parent ebbb9cd commit 699e101

File tree

5 files changed

+96
-85
lines changed

5 files changed

+96
-85
lines changed

clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -312,18 +312,6 @@ ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State,
312312
return setErrnoState(State, MustBeChecked);
313313
}
314314

315-
const NoteTag *getNoteTagForStdSuccess(CheckerContext &C, llvm::StringRef Fn) {
316-
return getErrnoNoteTag(
317-
C, llvm::formatv(
318-
"'errno' may be undefined after successful call to '{0}'", Fn));
319-
}
320-
321-
const NoteTag *getNoteTagForStdMustBeChecked(CheckerContext &C,
322-
llvm::StringRef Fn) {
323-
return getErrnoNoteTag(
324-
C, llvm::formatv("'{0}' indicates failure only by setting 'errno'", Fn));
325-
}
326-
327315
} // namespace errno_modeling
328316
} // namespace ento
329317
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message);
8484

8585
/// Set errno state for the common case when a standard function is successful.
8686
/// Set \c ErrnoCheckState to \c MustNotBeChecked (the \c errno value is not
87-
/// affected). At the state transition a note tag created by
88-
/// \c getNoteTagForStdSuccess can be used.
87+
/// affected).
8988
ProgramStateRef setErrnoForStdSuccess(ProgramStateRef State, CheckerContext &C);
9089

9190
/// Set errno state for the common case when a standard function fails.
@@ -100,23 +99,10 @@ ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C,
10099
/// Set errno state for the common case when a standard function indicates
101100
/// failure only by \c errno. Sets \c ErrnoCheckState to \c MustBeChecked, and
102101
/// invalidates the errno region (clear of previous value).
103-
/// At the state transition a note tag created by
104-
/// \c getNoteTagForStdMustBeChecked can be used.
105102
/// \arg \c InvalE Expression that causes invalidation of \c errno.
106103
ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State,
107104
CheckerContext &C, const Expr *InvalE);
108105

109-
/// Generate the note tag that can be applied at the state generated by
110-
/// \c setErrnoForStdSuccess .
111-
/// \arg \c Fn Name of the (standard) function that is modeled.
112-
const NoteTag *getNoteTagForStdSuccess(CheckerContext &C, llvm::StringRef Fn);
113-
114-
/// Generate the note tag that can be applied at the state generated by
115-
/// \c setErrnoStdMustBeChecked .
116-
/// \arg \c Fn Name of the (standard) function that is modeled.
117-
const NoteTag *getNoteTagForStdMustBeChecked(CheckerContext &C,
118-
llvm::StringRef Fn);
119-
120106
} // namespace errno_modeling
121107
} // namespace ento
122108
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,11 @@ class StdLibraryFunctionsChecker
533533
virtual ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
534534
const Summary &Summary,
535535
CheckerContext &C) const = 0;
536-
/// Get a NoteTag about the changes made to 'errno' and the possible bug.
537-
/// It may return \c nullptr (if no bug report from \c ErrnoChecker is
538-
/// expected).
539-
virtual const NoteTag *describe(CheckerContext &C,
540-
StringRef FunctionName) const {
541-
return nullptr;
542-
}
536+
/// Get a description about what happens with 'errno' here and how it causes
537+
/// a later bug report created by ErrnoChecker.
538+
/// Empty return value means that 'errno' related bug may not happen from
539+
/// the current analyzed function.
540+
virtual const std::string describe(CheckerContext &C) const { return ""; }
543541

544542
virtual ~ErrnoConstraintBase() {}
545543

@@ -596,7 +594,8 @@ class StdLibraryFunctionsChecker
596594
};
597595

598596
/// Set errno constraint at success cases of standard functions.
599-
/// Success case: 'errno' is not allowed to be used.
597+
/// Success case: 'errno' is not allowed to be used because the value is
598+
/// undefined after successful call.
600599
/// \c ErrnoChecker can emit bug report after such a function call if errno
601600
/// is used.
602601
class SuccessErrnoConstraint : public ErrnoConstraintBase {
@@ -607,12 +606,15 @@ class StdLibraryFunctionsChecker
607606
return errno_modeling::setErrnoForStdSuccess(State, C);
608607
}
609608

610-
const NoteTag *describe(CheckerContext &C,
611-
StringRef FunctionName) const override {
612-
return errno_modeling::getNoteTagForStdSuccess(C, FunctionName);
609+
const std::string describe(CheckerContext &C) const {
610+
return "'errno' becomes undefined after the call";
613611
}
614612
};
615613

614+
/// Set errno constraint at functions that indicate failure only with 'errno'.
615+
/// In this case 'errno' is required to be observed.
616+
/// \c ErrnoChecker can emit bug report after such a function call if errno
617+
/// is overwritten without a read before.
616618
class ErrnoMustBeCheckedConstraint : public ErrnoConstraintBase {
617619
public:
618620
ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
@@ -622,9 +624,8 @@ class StdLibraryFunctionsChecker
622624
Call.getOriginExpr());
623625
}
624626

625-
const NoteTag *describe(CheckerContext &C,
626-
StringRef FunctionName) const override {
627-
return errno_modeling::getNoteTagForStdMustBeChecked(C, FunctionName);
627+
const std::string describe(CheckerContext &C) const {
628+
return "reading 'errno' is required to find out if the call has failed";
628629
}
629630
};
630631

@@ -1392,17 +1393,28 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call,
13921393
// Still add these note tags, the other checker should add only its
13931394
// specialized note tags. These general note tags are handled always by
13941395
// StdLibraryFunctionsChecker.
1396+
13951397
ExplodedNode *Pred = Node;
1396-
if (!Case.getNote().empty()) {
1397-
const SVal RV = Call.getReturnValue();
1398-
// If there is a description for this execution branch (summary case),
1399-
// use it as a note tag.
1400-
std::string Note =
1401-
llvm::formatv(Case.getNote().str().c_str(),
1402-
cast<NamedDecl>(Call.getDecl())->getDeclName());
1403-
if (Summary.getInvalidationKd() == EvalCallAsPure) {
1398+
DeclarationName FunctionName =
1399+
cast<NamedDecl>(Call.getDecl())->getDeclName();
1400+
1401+
std::string ErrnoNote = Case.getErrnoConstraint().describe(C);
1402+
std::string CaseNote;
1403+
if (Case.getNote().empty()) {
1404+
if (!ErrnoNote.empty())
1405+
ErrnoNote =
1406+
llvm::formatv("After calling '{0}' {1}", FunctionName, ErrnoNote);
1407+
} else {
1408+
CaseNote = llvm::formatv(Case.getNote().str().c_str(), FunctionName);
1409+
}
1410+
const SVal RV = Call.getReturnValue();
1411+
1412+
if (Summary.getInvalidationKd() == EvalCallAsPure) {
1413+
// Do not expect that errno is interesting (the "pure" functions do not
1414+
// affect it).
1415+
if (!CaseNote.empty()) {
14041416
const NoteTag *Tag = C.getNoteTag(
1405-
[Node, Note, RV](PathSensitiveBugReport &BR) -> std::string {
1417+
[Node, CaseNote, RV](PathSensitiveBugReport &BR) -> std::string {
14061418
// Try to omit the note if we know in advance which branch is
14071419
// taken (this means, only one branch exists).
14081420
// This check is performed inside the lambda, after other
@@ -1414,37 +1426,42 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call,
14141426
// the successors). This is why this check is only used in the
14151427
// EvalCallAsPure case.
14161428
if (BR.isInteresting(RV) && Node->succ_size() > 1)
1417-
return Note;
1429+
return CaseNote;
14181430
return "";
14191431
});
14201432
Pred = C.addTransition(NewState, Pred, Tag);
1421-
} else {
1433+
}
1434+
} else {
1435+
if (!CaseNote.empty() || !ErrnoNote.empty()) {
14221436
const NoteTag *Tag =
1423-
C.getNoteTag([Note, RV](PathSensitiveBugReport &BR) -> std::string {
1424-
if (BR.isInteresting(RV))
1425-
return Note;
1437+
C.getNoteTag([CaseNote, ErrnoNote,
1438+
RV](PathSensitiveBugReport &BR) -> std::string {
1439+
// If 'errno' is interesting, show the user a note about the case
1440+
// (what happened at the function call) and about how 'errno'
1441+
// causes the problem. ErrnoChecker sets the errno (but not RV) to
1442+
// interesting.
1443+
// If only the return value is interesting, show only the case
1444+
// note.
1445+
std::optional<Loc> ErrnoLoc =
1446+
errno_modeling::getErrnoLoc(BR.getErrorNode()->getState());
1447+
bool ErrnoImportant = !ErrnoNote.empty() && ErrnoLoc &&
1448+
BR.isInteresting(ErrnoLoc->getAsRegion());
1449+
if (ErrnoImportant) {
1450+
BR.markNotInteresting(ErrnoLoc->getAsRegion());
1451+
if (CaseNote.empty())
1452+
return ErrnoNote;
1453+
return llvm::formatv("{0}; {1}", CaseNote, ErrnoNote);
1454+
} else {
1455+
if (BR.isInteresting(RV))
1456+
return CaseNote;
1457+
}
14261458
return "";
14271459
});
14281460
Pred = C.addTransition(NewState, Pred, Tag);
14291461
}
1430-
1431-
// Pred may be:
1432-
// - a nullpointer, if we reach an already existing node (theoretically);
1433-
// - a sink, when NewState is posteriorly overconstrained.
1434-
// In these situations we cannot add the errno note tag.
1435-
if (!Pred || Pred->isSink())
1436-
continue;
14371462
}
14381463

1439-
// If we can get a note tag for the errno change, add this additionally to
1440-
// the previous. This note is only about value of 'errno' and is displayed
1441-
// if 'errno' is interesting.
1442-
if (const auto *D = dyn_cast<FunctionDecl>(Call.getDecl()))
1443-
if (const NoteTag *NT =
1444-
Case.getErrnoConstraint().describe(C, D->getNameAsString()))
1445-
Pred = C.addTransition(NewState, Pred, NT);
1446-
1447-
// Add the transition if no note tag could be added.
1464+
// Add the transition if no note tag was added.
14481465
if (Pred == Node && NewState != State)
14491466
C.addTransition(NewState);
14501467
}
@@ -2038,7 +2055,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
20382055
ErrnoMustNotBeChecked, GenericSuccessMsg)
20392056
.Case({ArgumentCondition(1U, WithinRange, SingleValue(0)),
20402057
ReturnValueCondition(WithinRange, SingleValue(0))},
2041-
ErrnoMustNotBeChecked, GenericSuccessMsg)
2058+
ErrnoMustNotBeChecked,
2059+
"Assuming that argument 'size' to '{0}' is 0")
20422060
.ArgConstraint(NotNullBuffer(ArgNo(0), ArgNo(1), ArgNo(2)))
20432061
.ArgConstraint(NotNull(ArgNo(3)))
20442062
.ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1),

clang/test/Analysis/errno-stdlibraryfunctions-notes.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ void clang_analyzer_warnIfReached();
1515
void test1() {
1616
access("path", 0);
1717
access("path", 0);
18-
// expected-note@-1{{'errno' may be undefined after successful call to 'access'}}
18+
// expected-note@-1{{Assuming that 'access' is successful; 'errno' becomes undefined after the call}}
1919
if (errno != 0) {
2020
// expected-warning@-1{{An undefined value may be read from 'errno'}}
2121
// expected-note@-2{{An undefined value may be read from 'errno'}}
@@ -39,7 +39,7 @@ void test2() {
3939
void test3() {
4040
if (access("path", 0) != -1) {
4141
// Success path.
42-
// expected-note@-2{{'errno' may be undefined after successful call to 'access'}}
42+
// expected-note@-2{{Assuming that 'access' is successful; 'errno' becomes undefined after the call}}
4343
// expected-note@-3{{Taking true branch}}
4444
if (errno != 0) {
4545
// expected-warning@-1{{An undefined value may be read from 'errno'}}

clang/test/Analysis/stream-errno-note.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
void check_fopen(void) {
1212
FILE *F = fopen("xxx", "r");
13-
// expected-note@-1{{'errno' may be undefined after successful call to 'fopen'}}
13+
// expected-note@-1{{Assuming that 'fopen' is successful; 'errno' becomes undefined after the call}}
1414
// expected-note@+2{{'F' is non-null}}
1515
// expected-note@+1{{Taking false branch}}
1616
if (!F)
@@ -22,7 +22,7 @@ void check_fopen(void) {
2222

2323
void check_tmpfile(void) {
2424
FILE *F = tmpfile();
25-
// expected-note@-1{{'errno' may be undefined after successful call to 'tmpfile'}}
25+
// expected-note@-1{{Assuming that 'tmpfile' is successful; 'errno' becomes undefined after the call}}
2626
// expected-note@+2{{'F' is non-null}}
2727
// expected-note@+1{{Taking false branch}}
2828
if (!F)
@@ -39,7 +39,7 @@ void check_freopen(void) {
3939
if (!F)
4040
return;
4141
F = freopen("xxx", "w", F);
42-
// expected-note@-1{{'errno' may be undefined after successful call to 'freopen'}}
42+
// expected-note@-1{{Assuming that 'freopen' is successful; 'errno' becomes undefined after the call}}
4343
// expected-note@+2{{'F' is non-null}}
4444
// expected-note@+1{{Taking false branch}}
4545
if (!F)
@@ -56,7 +56,7 @@ void check_fclose(void) {
5656
if (!F)
5757
return;
5858
(void)fclose(F);
59-
// expected-note@-1{{'errno' may be undefined after successful call to 'fclose'}}
59+
// expected-note@-1{{Assuming that 'fclose' is successful; 'errno' becomes undefined after the call}}
6060
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
6161
// expected-note@-1{{An undefined value may be read from 'errno'}}
6262
}
@@ -69,7 +69,7 @@ void check_fread(void) {
6969
if (!F)
7070
return;
7171
(void)fread(Buf, 1, 10, F);
72-
// expected-note@-1{{'errno' may be undefined after successful call to 'fread'}}
72+
// expected-note@-1{{Assuming that 'fread' is successful; 'errno' becomes undefined after the call}}
7373
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
7474
// expected-note@-1{{An undefined value may be read from 'errno'}}
7575
(void)fclose(F);
@@ -83,7 +83,7 @@ void check_fread_size0(void) {
8383
if (!F)
8484
return;
8585
fread(Buf, 0, 1, F);
86-
// expected-note@-1{{'errno' may be undefined after successful call to 'fread'}}
86+
// expected-note@-1{{Assuming that argument 'size' to 'fread' is 0; 'errno' becomes undefined after the call}}
8787
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
8888
// expected-note@-1{{An undefined value may be read from 'errno'}}
8989
}
@@ -96,7 +96,7 @@ void check_fread_nmemb0(void) {
9696
if (!F)
9797
return;
9898
fread(Buf, 1, 0, F);
99-
// expected-note@-1{{'errno' may be undefined after successful call to 'fread'}}
99+
// expected-note@-1{{Assuming that 'fread' is successful; 'errno' becomes undefined after the call}}
100100
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
101101
// expected-note@-1{{An undefined value may be read from 'errno'}}
102102
}
@@ -109,7 +109,7 @@ void check_fwrite(void) {
109109
if (!F)
110110
return;
111111
int R = fwrite(Buf, 1, 10, F);
112-
// expected-note@-1{{'errno' may be undefined after successful call to 'fwrite'}}
112+
// expected-note@-1{{Assuming that 'fwrite' is successful; 'errno' becomes undefined after the call}}
113113
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
114114
// expected-note@-1{{An undefined value may be read from 'errno'}}
115115
(void)fclose(F);
@@ -122,7 +122,7 @@ void check_fseek(void) {
122122
if (!F)
123123
return;
124124
(void)fseek(F, 11, SEEK_SET);
125-
// expected-note@-1{{'errno' may be undefined after successful call to 'fseek'}}
125+
// expected-note@-1{{Assuming that 'fseek' is successful; 'errno' becomes undefined after the call}}
126126
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
127127
// expected-note@-1{{An undefined value may be read from 'errno'}}
128128
(void)fclose(F);
@@ -135,7 +135,7 @@ void check_rewind_errnocheck(void) {
135135
if (!F)
136136
return;
137137
errno = 0;
138-
rewind(F); // expected-note{{'rewind' indicates failure only by setting 'errno'}}
138+
rewind(F); // expected-note{{After calling 'rewind' reading 'errno' is required to find out if the call has failed}}
139139
fclose(F); // expected-warning{{Value of 'errno' was not checked and may be overwritten by function 'fclose' [alpha.unix.Errno]}}
140140
// expected-note@-1{{Value of 'errno' was not checked and may be overwritten by function 'fclose'}}
141141
}
@@ -147,8 +147,27 @@ void check_fileno(void) {
147147
if (!F)
148148
return;
149149
fileno(F);
150-
// expected-note@-1{{'errno' may be undefined after successful call to 'fileno'}}
150+
// expected-note@-1{{Assuming that 'fileno' is successful; 'errno' becomes undefined after the call}}
151151
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
152152
// expected-note@-1{{An undefined value may be read from 'errno'}}
153153
(void)fclose(F);
154154
}
155+
156+
void check_fwrite_zeroarg(size_t Siz) {
157+
char Buf[] = "0123456789";
158+
FILE *F = tmpfile();
159+
// expected-note@+2{{'F' is non-null}}
160+
// expected-note@+1{{Taking false branch}}
161+
if (!F)
162+
return;
163+
errno = 0;
164+
int R = fwrite(Buf, Siz, 1, F);
165+
// expected-note@-1{{Assuming that argument 'size' to 'fwrite' is 0; 'errno' becomes undefined after the call}}
166+
// expected-note@+2{{'R' is <= 0}}
167+
// expected-note@+1{{Taking true branch}}
168+
if (R <= 0) {
169+
if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
170+
// expected-note@-1{{An undefined value may be read from 'errno'}}
171+
}
172+
(void)fclose(F);
173+
}

0 commit comments

Comments
 (0)