Skip to content

Commit 6c51270

Browse files
author
Balazs Benics
committed
[analyzer][NFC] Introduce CallDescription::matches() in addition to isCalled()
This patch introduces `CallDescription::matches()` member function, accepting a `CallEvent`. Semantically, `Call.isCalled(CD)` is the same as `CD.matches(Call)`. The patch also introduces the `matchesAny()` variadic free function template. It accepts a `CallEvent` and at least one `CallDescription` to match against. Reviewed By: martong Differential Revision: https://reviews.llvm.org/D113590
1 parent d448fcd commit 6c51270

File tree

3 files changed

+112
-80
lines changed

3 files changed

+112
-80
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,33 @@ class CallDescription {
8383
/// It's false, if and only if we expect a single identifier, such as
8484
/// `getenv`. It's true for `std::swap`, or `my::detail::container::data`.
8585
bool hasQualifiedNameParts() const { return QualifiedName.size() > 1; }
86+
87+
/// @name Matching CallDescriptions against a CallEvent
88+
/// @{
89+
90+
/// Returns true if the CallEvent is a call to a function that matches
91+
/// the CallDescription.
92+
///
93+
/// \note This function is not intended to be used to match Obj-C method
94+
/// calls.
95+
bool matches(const CallEvent &Call) const;
96+
97+
/// Returns true whether the CallEvent matches on any of the CallDescriptions
98+
/// supplied.
99+
///
100+
/// \note This function is not intended to be used to match Obj-C method
101+
/// calls.
102+
friend bool matchesAny(const CallEvent &Call, const CallDescription &CD1) {
103+
return CD1.matches(Call);
104+
}
105+
106+
/// \copydoc clang::ento::matchesAny(const CallEvent &, const CallDescription &)
107+
template <typename... Ts>
108+
friend bool matchesAny(const CallEvent &Call, const CallDescription &CD1,
109+
const Ts &...CDs) {
110+
return CD1.matches(Call) || matchesAny(Call, CDs...);
111+
}
112+
/// @}
86113
};
87114

88115
/// An immutable map from CallDescriptions to arbitrary data. Provides a unified
@@ -113,7 +140,7 @@ template <typename T> class CallDescriptionMap {
113140
// Slow path: linear lookup.
114141
// TODO: Implement some sort of fast path.
115142
for (const std::pair<CallDescription, T> &I : LinearMap)
116-
if (Call.isCalled(I.first))
143+
if (I.first.matches(Call))
117144
return &I.second;
118145

119146
return nullptr;

clang/lib/StaticAnalyzer/Core/CallDescription.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
1616
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
17+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
1718
#include "llvm/ADT/ArrayRef.h"
1819
#include "llvm/ADT/Optional.h"
1920

@@ -47,6 +48,88 @@ ento::CallDescription::CallDescription(
4748
Optional<size_t> RequiredParams /*= None*/)
4849
: CallDescription(0, QualifiedName, RequiredArgs, RequiredParams) {}
4950

51+
bool ento::CallDescription::matches(const CallEvent &Call) const {
52+
// FIXME: Add ObjC Message support.
53+
if (Call.getKind() == CE_ObjCMessage)
54+
return false;
55+
56+
const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
57+
if (!FD)
58+
return false;
59+
60+
if (Flags & CDF_MaybeBuiltin) {
61+
return CheckerContext::isCLibraryFunction(FD, getFunctionName()) &&
62+
(!RequiredArgs || RequiredArgs <= Call.getNumArgs()) &&
63+
(!RequiredParams || RequiredParams <= Call.parameters().size());
64+
}
65+
66+
if (!II.hasValue()) {
67+
II = &Call.getState()->getStateManager().getContext().Idents.get(
68+
getFunctionName());
69+
}
70+
71+
const auto MatchNameOnly = [](const CallDescription &CD,
72+
const NamedDecl *ND) -> bool {
73+
DeclarationName Name = ND->getDeclName();
74+
if (const auto *II = Name.getAsIdentifierInfo())
75+
return II == CD.II.getValue(); // Fast case.
76+
77+
// Fallback to the slow stringification and comparison for:
78+
// C++ overloaded operators, constructors, destructors, etc.
79+
// FIXME This comparison is way SLOWER than comparing pointers.
80+
// At some point in the future, we should compare FunctionDecl pointers.
81+
return Name.getAsString() == CD.getFunctionName();
82+
};
83+
84+
const auto ExactMatchArgAndParamCounts =
85+
[](const CallEvent &Call, const CallDescription &CD) -> bool {
86+
const bool ArgsMatch =
87+
!CD.RequiredArgs || CD.RequiredArgs == Call.getNumArgs();
88+
const bool ParamsMatch =
89+
!CD.RequiredParams || CD.RequiredParams == Call.parameters().size();
90+
return ArgsMatch && ParamsMatch;
91+
};
92+
93+
const auto MatchQualifiedNameParts = [](const CallDescription &CD,
94+
const Decl *D) -> bool {
95+
const auto FindNextNamespaceOrRecord =
96+
[](const DeclContext *Ctx) -> const DeclContext * {
97+
while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx))
98+
Ctx = Ctx->getParent();
99+
return Ctx;
100+
};
101+
102+
auto QualifierPartsIt = CD.begin_qualified_name_parts();
103+
const auto QualifierPartsEndIt = CD.end_qualified_name_parts();
104+
105+
// Match namespace and record names. Skip unrelated names if they don't
106+
// match.
107+
const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext());
108+
for (; Ctx && QualifierPartsIt != QualifierPartsEndIt;
109+
Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) {
110+
// If not matched just continue and try matching for the next one.
111+
if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt)
112+
continue;
113+
++QualifierPartsIt;
114+
}
115+
116+
// We matched if we consumed all expected qualifier segments.
117+
return QualifierPartsIt == QualifierPartsEndIt;
118+
};
119+
120+
// Let's start matching...
121+
if (!ExactMatchArgAndParamCounts(Call, *this))
122+
return false;
123+
124+
if (!MatchNameOnly(*this, FD))
125+
return false;
126+
127+
if (!hasQualifiedNameParts())
128+
return true;
129+
130+
return MatchQualifiedNameParts(*this, FD);
131+
}
132+
50133
ento::CallDescriptionSet::CallDescriptionSet(
51134
std::initializer_list<CallDescription> &&List) {
52135
Impl.LinearMap.reserve(List.size());

clang/lib/StaticAnalyzer/Core/CallEvent.cpp

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -304,85 +304,7 @@ ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit,
304304
}
305305

306306
bool CallEvent::isCalled(const CallDescription &CD) const {
307-
// FIXME: Add ObjC Message support.
308-
if (getKind() == CE_ObjCMessage)
309-
return false;
310-
311-
const auto *FD = dyn_cast_or_null<FunctionDecl>(getDecl());
312-
if (!FD)
313-
return false;
314-
315-
if (CD.Flags & CDF_MaybeBuiltin) {
316-
return CheckerContext::isCLibraryFunction(FD, CD.getFunctionName()) &&
317-
(!CD.RequiredArgs || CD.RequiredArgs <= getNumArgs()) &&
318-
(!CD.RequiredParams || CD.RequiredParams <= parameters().size());
319-
}
320-
321-
if (!CD.II.hasValue()) {
322-
CD.II = &getState()->getStateManager().getContext().Idents.get(
323-
CD.getFunctionName());
324-
}
325-
326-
const auto MatchNameOnly = [](const CallDescription &CD,
327-
const NamedDecl *ND) -> bool {
328-
DeclarationName Name = ND->getDeclName();
329-
if (const auto *II = Name.getAsIdentifierInfo())
330-
return II == CD.II.getValue(); // Fast case.
331-
332-
// Fallback to the slow stringification and comparison for:
333-
// C++ overloaded operators, constructors, destructors, etc.
334-
// FIXME This comparison is way SLOWER than comparing pointers.
335-
// At some point in the future, we should compare FunctionDecl pointers.
336-
return Name.getAsString() == CD.getFunctionName();
337-
};
338-
339-
const auto ExactMatchArgAndParamCounts =
340-
[](const CallEvent &Call, const CallDescription &CD) -> bool {
341-
const bool ArgsMatch =
342-
!CD.RequiredArgs || CD.RequiredArgs == Call.getNumArgs();
343-
const bool ParamsMatch =
344-
!CD.RequiredParams || CD.RequiredParams == Call.parameters().size();
345-
return ArgsMatch && ParamsMatch;
346-
};
347-
348-
const auto MatchQualifiedNameParts = [](const CallDescription &CD,
349-
const Decl *D) -> bool {
350-
const auto FindNextNamespaceOrRecord =
351-
[](const DeclContext *Ctx) -> const DeclContext * {
352-
while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx))
353-
Ctx = Ctx->getParent();
354-
return Ctx;
355-
};
356-
357-
auto QualifierPartsIt = CD.begin_qualified_name_parts();
358-
const auto QualifierPartsEndIt = CD.end_qualified_name_parts();
359-
360-
// Match namespace and record names. Skip unrelated names if they don't
361-
// match.
362-
const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext());
363-
for (; Ctx && QualifierPartsIt != QualifierPartsEndIt;
364-
Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) {
365-
// If not matched just continue and try matching for the next one.
366-
if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt)
367-
continue;
368-
++QualifierPartsIt;
369-
}
370-
371-
// We matched if we consumed all expected qualifier segments.
372-
return QualifierPartsIt == QualifierPartsEndIt;
373-
};
374-
375-
// Let's start matching...
376-
if (!ExactMatchArgAndParamCounts(*this, CD))
377-
return false;
378-
379-
if (!MatchNameOnly(CD, FD))
380-
return false;
381-
382-
if (!CD.hasQualifiedNameParts())
383-
return true;
384-
385-
return MatchQualifiedNameParts(CD, FD);
307+
return CD.matches(*this);
386308
}
387309

388310
SVal CallEvent::getArgSVal(unsigned Index) const {

0 commit comments

Comments
 (0)