Skip to content

Commit ca4a405

Browse files
authored
[analyzer] Refactor recognition of the errno getter functions (#91531)
There are many environments where `errno` is a macro that expands to something like `(*__errno())` (different standard library implementations use different names instead of "__errno"). In these environments the ErrnoModeling checker creates a symbolic region which will be used to represent the return value of this "get the location of errno" function. Previously this symbol was only created when the checker was able to find the declaration of the "get the location of errno" function; but this commit eliminates the complex logic that was responsible for this and always creates the symbolic region when `errno` is not available as a "regular" global variable. This significantly simplifies a code and only introduces a minimal performance reduction (one extra symbol) in the case when `errno` is not declared (neither as a variable nor as a function). In addition to this simplification, this commit specifies that the `CallDescription`s for the "get the location of errno" functions are matched in `CDM::CLibrary` mode. (This was my original goal, but I was sidetracked by resolving a FIXME above the `CallDescriptionSet` in `ErrnoModeling.cpp`.) This change is very close to being NFC, but it fixes weird corner cases like the handling of a C++ method that happens to be named "__errno()" (previously it could've been recognized as an errno location getter function).
1 parent afba3da commit ca4a405

File tree

4 files changed

+56
-104
lines changed

4 files changed

+56
-104
lines changed

clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ void ErrnoChecker::checkPreCall(const CallEvent &Call,
205205
// Probably 'strerror'?
206206
if (CallF->isExternC() && CallF->isGlobal() &&
207207
C.getSourceManager().isInSystemHeader(CallF->getLocation()) &&
208-
!isErrno(CallF)) {
208+
!isErrnoLocationCall(Call)) {
209209
if (getErrnoState(C.getState()) == MustBeChecked) {
210210
std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
211211
assert(ErrnoLoc && "ErrnoLoc should exist if an errno state is set.");

clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp

Lines changed: 43 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ namespace {
3939
// Name of the "errno" variable.
4040
// FIXME: Is there a system where it is not called "errno" but is a variable?
4141
const char *ErrnoVarName = "errno";
42+
4243
// Names of functions that return a location of the "errno" value.
4344
// FIXME: Are there other similar function names?
44-
const char *ErrnoLocationFuncNames[] = {"__errno_location", "___errno",
45-
"__errno", "_errno", "__error"};
45+
CallDescriptionSet ErrnoLocationCalls{
46+
{CDM::CLibrary, {"__errno_location"}, 0, 0},
47+
{CDM::CLibrary, {"___errno"}, 0, 0},
48+
{CDM::CLibrary, {"__errno"}, 0, 0},
49+
{CDM::CLibrary, {"_errno"}, 0, 0},
50+
{CDM::CLibrary, {"__error"}, 0, 0}};
4651

4752
class ErrnoModeling
4853
: public Checker<check::ASTDecl<TranslationUnitDecl>, check::BeginFunction,
@@ -54,16 +59,10 @@ class ErrnoModeling
5459
void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const;
5560
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
5661

57-
// The declaration of an "errno" variable or "errno location" function.
58-
mutable const Decl *ErrnoDecl = nullptr;
59-
6062
private:
61-
// FIXME: Names from `ErrnoLocationFuncNames` are used to build this set.
62-
CallDescriptionSet ErrnoLocationCalls{{{"__errno_location"}, 0, 0},
63-
{{"___errno"}, 0, 0},
64-
{{"__errno"}, 0, 0},
65-
{{"_errno"}, 0, 0},
66-
{{"__error"}, 0, 0}};
63+
// The declaration of an "errno" variable on systems where errno is
64+
// represented by a variable (and not a function that queries its location).
65+
mutable const VarDecl *ErrnoDecl = nullptr;
6766
};
6867

6968
} // namespace
@@ -74,9 +73,13 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoRegion, const MemRegion *)
7473

7574
REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoState, errno_modeling::ErrnoCheckState)
7675

77-
/// Search for a variable called "errno" in the AST.
78-
/// Return nullptr if not found.
79-
static const VarDecl *getErrnoVar(ASTContext &ACtx) {
76+
void ErrnoModeling::checkASTDecl(const TranslationUnitDecl *D,
77+
AnalysisManager &Mgr, BugReporter &BR) const {
78+
// Try to find the declaration of the external variable `int errno;`.
79+
// There are also C library implementations, where the `errno` location is
80+
// accessed via a function that returns its address; in those environments
81+
// this callback has no effect.
82+
ASTContext &ACtx = Mgr.getASTContext();
8083
IdentifierInfo &II = ACtx.Idents.get(ErrnoVarName);
8184
auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II);
8285
auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) {
@@ -86,47 +89,8 @@ static const VarDecl *getErrnoVar(ASTContext &ACtx) {
8689
VD->getType().getCanonicalType() == ACtx.IntTy;
8790
return false;
8891
});
89-
if (Found == LookupRes.end())
90-
return nullptr;
91-
92-
return cast<VarDecl>(*Found);
93-
}
94-
95-
/// Search for a function with a specific name that is used to return a pointer
96-
/// to "errno".
97-
/// Return nullptr if no such function was found.
98-
static const FunctionDecl *getErrnoFunc(ASTContext &ACtx) {
99-
SmallVector<const Decl *> LookupRes;
100-
for (StringRef ErrnoName : ErrnoLocationFuncNames) {
101-
IdentifierInfo &II = ACtx.Idents.get(ErrnoName);
102-
llvm::append_range(LookupRes, ACtx.getTranslationUnitDecl()->lookup(&II));
103-
}
104-
105-
auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) {
106-
if (auto *FD = dyn_cast<FunctionDecl>(D))
107-
return ACtx.getSourceManager().isInSystemHeader(FD->getLocation()) &&
108-
FD->isExternC() && FD->getNumParams() == 0 &&
109-
FD->getReturnType().getCanonicalType() ==
110-
ACtx.getPointerType(ACtx.IntTy);
111-
return false;
112-
});
113-
if (Found == LookupRes.end())
114-
return nullptr;
115-
116-
return cast<FunctionDecl>(*Found);
117-
}
118-
119-
void ErrnoModeling::checkASTDecl(const TranslationUnitDecl *D,
120-
AnalysisManager &Mgr, BugReporter &BR) const {
121-
// Try to find an usable `errno` value.
122-
// It can be an external variable called "errno" or a function that returns a
123-
// pointer to the "errno" value. This function can have different names.
124-
// The actual case is dependent on the C library implementation, we
125-
// can only search for a match in one of these variations.
126-
// We assume that exactly one of these cases might be true.
127-
ErrnoDecl = getErrnoVar(Mgr.getASTContext());
128-
if (!ErrnoDecl)
129-
ErrnoDecl = getErrnoFunc(Mgr.getASTContext());
92+
if (Found != LookupRes.end())
93+
ErrnoDecl = cast<VarDecl>(*Found);
13094
}
13195

13296
void ErrnoModeling::checkBeginFunction(CheckerContext &C) const {
@@ -136,53 +100,50 @@ void ErrnoModeling::checkBeginFunction(CheckerContext &C) const {
136100
ASTContext &ACtx = C.getASTContext();
137101
ProgramStateRef State = C.getState();
138102

139-
if (const auto *ErrnoVar = dyn_cast_or_null<VarDecl>(ErrnoDecl)) {
140-
// There is an external 'errno' variable.
141-
// Use its memory region.
142-
// The memory region for an 'errno'-like variable is allocated in system
143-
// space by MemRegionManager.
144-
const MemRegion *ErrnoR =
145-
State->getRegion(ErrnoVar, C.getLocationContext());
103+
const MemRegion *ErrnoR = nullptr;
104+
105+
if (ErrnoDecl) {
106+
// There is an external 'errno' variable, so we can simply use the memory
107+
// region that's associated with it.
108+
ErrnoR = State->getRegion(ErrnoDecl, C.getLocationContext());
146109
assert(ErrnoR && "Memory region should exist for the 'errno' variable.");
147-
State = State->set<ErrnoRegion>(ErrnoR);
148-
State =
149-
errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant);
150-
C.addTransition(State);
151-
} else if (ErrnoDecl) {
152-
assert(isa<FunctionDecl>(ErrnoDecl) && "Invalid errno location function.");
153-
// There is a function that returns the location of 'errno'.
154-
// We must create a memory region for it in system space.
155-
// Currently a symbolic region is used with an artifical symbol.
156-
// FIXME: It is better to have a custom (new) kind of MemRegion for such
157-
// cases.
110+
} else {
111+
// There is no 'errno' variable, so create a new symbolic memory region
112+
// that can be used to model the return value of the "get the location of
113+
// errno" internal functions.
114+
// NOTE: this `SVal` is created even if errno is not defined or used.
158115
SValBuilder &SVB = C.getSValBuilder();
159116
MemRegionManager &RMgr = C.getStateManager().getRegionManager();
160117

161118
const MemSpaceRegion *GlobalSystemSpace =
162119
RMgr.getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind);
163120

164121
// Create an artifical symbol for the region.
165-
// It is not possible to associate a statement or expression in this case.
122+
// Note that it is not possible to associate a statement or expression in
123+
// this case and the `symbolTag` (opaque pointer tag) is just the address
124+
// of the data member `ErrnoDecl` of the singleton `ErrnoModeling` checker
125+
// object.
166126
const SymbolConjured *Sym = SVB.conjureSymbol(
167127
nullptr, C.getLocationContext(),
168128
ACtx.getLValueReferenceType(ACtx.IntTy), C.blockCount(), &ErrnoDecl);
169129

170130
// The symbolic region is untyped, create a typed sub-region in it.
171131
// The ElementRegion is used to make the errno region a typed region.
172-
const MemRegion *ErrnoR = RMgr.getElementRegion(
132+
ErrnoR = RMgr.getElementRegion(
173133
ACtx.IntTy, SVB.makeZeroArrayIndex(),
174134
RMgr.getSymbolicRegion(Sym, GlobalSystemSpace), C.getASTContext());
175-
State = State->set<ErrnoRegion>(ErrnoR);
176-
State =
177-
errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant);
178-
C.addTransition(State);
179135
}
136+
assert(ErrnoR);
137+
State = State->set<ErrnoRegion>(ErrnoR);
138+
State =
139+
errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant);
140+
C.addTransition(State);
180141
}
181142

182143
bool ErrnoModeling::evalCall(const CallEvent &Call, CheckerContext &C) const {
183144
// Return location of "errno" at a call to an "errno address returning"
184145
// function.
185-
if (ErrnoLocationCalls.contains(Call)) {
146+
if (errno_modeling::isErrnoLocationCall(Call)) {
186147
ProgramStateRef State = C.getState();
187148

188149
const MemRegion *ErrnoR = State->get<ErrnoRegion>();
@@ -260,14 +221,8 @@ ProgramStateRef clearErrnoState(ProgramStateRef State) {
260221
return setErrnoState(State, Irrelevant);
261222
}
262223

263-
bool isErrno(const Decl *D) {
264-
if (const auto *VD = dyn_cast_or_null<VarDecl>(D))
265-
if (const IdentifierInfo *II = VD->getIdentifier())
266-
return II->getName() == ErrnoVarName;
267-
if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D))
268-
if (const IdentifierInfo *II = FD->getIdentifier())
269-
return llvm::is_contained(ErrnoLocationFuncNames, II->getName());
270-
return false;
224+
bool isErrnoLocationCall(const CallEvent &CE) {
225+
return ErrnoLocationCalls.contains(CE);
271226
}
272227

273228
const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message) {

clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,9 @@ ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState);
7171
/// Clear state of errno (make it irrelevant).
7272
ProgramStateRef clearErrnoState(ProgramStateRef State);
7373

74-
/// Determine if a `Decl` node related to 'errno'.
75-
/// This is true if the declaration is the errno variable or a function
76-
/// that returns a pointer to the 'errno' value (usually the 'errno' macro is
77-
/// defined with this function). \p D is not required to be a canonical
78-
/// declaration.
79-
bool isErrno(const Decl *D);
74+
/// Determine if `Call` is a call to an internal function that returns the
75+
/// location of `errno` (in environments where errno is accessed this way).
76+
bool isErrnoLocationCall(const CallEvent &Call);
8077

8178
/// Create a NoteTag that displays the message if the 'errno' memory region is
8279
/// marked as interesting, and resets the interestingness.

clang/test/Analysis/memory-model.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ void var_simple_ref() {
3434
}
3535

3636
void var_simple_ptr(int *a) {
37-
clang_analyzer_dump(a); // expected-warning {{SymRegion{reg_$0<int * a>}}}
38-
clang_analyzer_dumpExtent(a); // expected-warning {{extent_$1{SymRegion{reg_$0<int * a>}}}}
39-
clang_analyzer_dumpElementCount(a); // expected-warning {{(extent_$1{SymRegion{reg_$0<int * a>}}) / 4}}
37+
clang_analyzer_dump(a); // expected-warning {{SymRegion{reg_$1<int * a>}}}
38+
clang_analyzer_dumpExtent(a); // expected-warning {{extent_$2{SymRegion{reg_$1<int * a>}}}}
39+
clang_analyzer_dumpElementCount(a); // expected-warning {{(extent_$2{SymRegion{reg_$1<int * a>}}) / 4}}
4040
}
4141

4242
void var_array() {
@@ -53,9 +53,9 @@ void string() {
5353
}
5454

5555
void struct_simple_ptr(S *a) {
56-
clang_analyzer_dump(a); // expected-warning {{SymRegion{reg_$0<S * a>}}}
57-
clang_analyzer_dumpExtent(a); // expected-warning {{extent_$1{SymRegion{reg_$0<S * a>}}}}
58-
clang_analyzer_dumpElementCount(a); // expected-warning {{(extent_$1{SymRegion{reg_$0<S * a>}}) / 4}}
56+
clang_analyzer_dump(a); // expected-warning {{SymRegion{reg_$1<S * a>}}}
57+
clang_analyzer_dumpExtent(a); // expected-warning {{extent_$2{SymRegion{reg_$1<S * a>}}}}
58+
clang_analyzer_dumpElementCount(a); // expected-warning {{(extent_$2{SymRegion{reg_$1<S * a>}}) / 4}}
5959
}
6060

6161
void field_ref(S a) {
@@ -65,9 +65,9 @@ void field_ref(S a) {
6565
}
6666

6767
void field_ptr(S *a) {
68-
clang_analyzer_dump(&a->f); // expected-warning {{Element{SymRegion{reg_$0<S * a>},0 S64b,struct S}.f}}
69-
clang_analyzer_dumpExtent(&a->f); // expected-warning {{extent_$1{SymRegion{reg_$0<S * a>}}}}
70-
clang_analyzer_dumpElementCount(&a->f); // expected-warning {{(extent_$1{SymRegion{reg_$0<S * a>}}) / 4U}}
68+
clang_analyzer_dump(&a->f); // expected-warning {{Element{SymRegion{reg_$1<S * a>},0 S64b,struct S}.f}}
69+
clang_analyzer_dumpExtent(&a->f); // expected-warning {{extent_$2{SymRegion{reg_$1<S * a>}}}}
70+
clang_analyzer_dumpElementCount(&a->f); // expected-warning {{(extent_$2{SymRegion{reg_$1<S * a>}}) / 4U}}
7171
}
7272

7373
void symbolic_array() {

0 commit comments

Comments
 (0)