Skip to content

Commit

Permalink
[ASTMatchers] Allow hasName() to look through inline namespaces
Browse files Browse the repository at this point in the history
Summary:
Allow hasName() to look through inline namespaces.
This will fix the interaction between some clang-tidy checks and libc++.

libc++ defines names in an inline namespace named std::<version_#>.
When we try to match a name using hasName("std::xxx") it fails to match and the clang-tidy check does not work.

Reviewers: klimek

Subscribers: klimek, cfe-commits

Differential Revision: http://reviews.llvm.org/D15506

llvm-svn: 259898
  • Loading branch information
sbenzaquen committed Feb 5, 2016
1 parent 5dde1d2 commit 8e566f3
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 22 deletions.
10 changes: 9 additions & 1 deletion clang/include/clang/ASTMatchers/ASTMatchersInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -651,12 +651,20 @@ class HasNameMatcher : public SingleNodeMatcherInterface<NamedDecl> {
/// matches.
bool matchesNodeUnqualified(const NamedDecl &Node) const;

/// \brief Full match routine
///
/// Fast implementation for the simple case of a named declaration at
/// namespace or RecordDecl scope.
/// It is slower than matchesNodeUnqualified, but faster than
/// matchesNodeFullSlow.
bool matchesNodeFullFast(const NamedDecl &Node) const;

/// \brief Full match routine
///
/// It generates the fully qualified name of the declaration (which is
/// expensive) before trying to match.
/// It is slower but simple and works on all cases.
bool matchesNodeFull(const NamedDecl &Node) const;
bool matchesNodeFullSlow(const NamedDecl &Node) const;

const bool UseUnqualifiedMatch;
const std::string Name;
Expand Down
146 changes: 125 additions & 21 deletions clang/lib/ASTMatchers/ASTMatchersInternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,45 +298,149 @@ HasNameMatcher::HasNameMatcher(StringRef NameRef)
assert(!Name.empty());
}

bool HasNameMatcher::matchesNodeUnqualified(const NamedDecl &Node) const {
assert(UseUnqualifiedMatch);
if (Node.getIdentifier()) {
// Simple name.
return Name == Node.getName();
namespace {

bool ConsumeNameSuffix(StringRef &FullName, StringRef Suffix) {
StringRef Name = FullName;
if (!Name.endswith(Suffix))
return false;
Name = Name.drop_back(Suffix.size());
if (!Name.empty()) {
if (!Name.endswith("::"))
return false;
Name = Name.drop_back(2);
}
FullName = Name;
return true;
}

bool ConsumeNodeName(StringRef &Name, const NamedDecl &Node) {
// Simple name.
if (Node.getIdentifier())
return ConsumeNameSuffix(Name, Node.getName());

if (Node.getDeclName()) {
// Name needs to be constructed.
llvm::SmallString<128> NodeName;
llvm::raw_svector_ostream OS(NodeName);
Node.printName(OS);
return Name == OS.str();
return ConsumeNameSuffix(Name, OS.str());
}
return false;

return ConsumeNameSuffix(Name, "(anonymous)");
}

bool HasNameMatcher::matchesNodeFull(const NamedDecl &Node) const {
llvm::SmallString<128> NodeName = StringRef("::");
llvm::raw_svector_ostream OS(NodeName);
Node.printQualifiedName(OS);
const StringRef FullName = OS.str();
} // namespace

bool HasNameMatcher::matchesNodeUnqualified(const NamedDecl &Node) const {
assert(UseUnqualifiedMatch);
StringRef NodeName = Name;
return ConsumeNodeName(NodeName, Node) && NodeName.empty();
}

bool HasNameMatcher::matchesNodeFullFast(const NamedDecl &Node) const {
// This function is copied and adapted from NamedDecl::printQualifiedName()
// By matching each part individually we optimize in a couple of ways:
// - We can exit early on the first failure.
// - We can skip inline/anonymous namespaces without another pass.
// - We print one name at a time, reducing the chance of overflowing the
// inlined space of the SmallString.
StringRef Pattern = Name;
const bool IsFullyQualified = Pattern.startswith("::");

// First, match the name.
if (!ConsumeNodeName(Pattern, Node))
return false;

// Try to match each declaration context.
// We are allowed to skip anonymous and inline namespaces if they don't match.
const DeclContext *Ctx = Node.getDeclContext();

if (Ctx->isFunctionOrMethod())
return Pattern.empty() && !IsFullyQualified;

for (; !Pattern.empty() && Ctx && isa<NamedDecl>(Ctx);
Ctx = Ctx->getParent()) {
if (const auto *ND = dyn_cast<NamespaceDecl>(Ctx)) {
StringRef NSName =
ND->isAnonymousNamespace() ? "(anonymous namespace)" : ND->getName();

// If it matches, continue.
if (ConsumeNameSuffix(Pattern, NSName))
continue;
// If it didn't match but we can skip it, continue.
if (ND->isAnonymousNamespace() || ND->isInline())
continue;

return false;
}
if (const auto *RD = dyn_cast<RecordDecl>(Ctx)) {
if (!isa<ClassTemplateSpecializationDecl>(Ctx)) {
if (RD->getIdentifier()) {
if (ConsumeNameSuffix(Pattern, RD->getName()))
continue;
} else {
llvm::SmallString<128> NodeName;
NodeName += StringRef("(anonymous ");
NodeName += RD->getKindName();
NodeName += ')';
if (ConsumeNameSuffix(Pattern, NodeName))
continue;
}

return false;
}
}

// We don't know how to deal with this DeclContext.
// Fallback to the slow version of the code.
return matchesNodeFullSlow(Node);
}

// If we are fully qualified, we must not have any leftover context.
if (IsFullyQualified && Ctx && isa<NamedDecl>(Ctx))
return false;

return Pattern.empty();
}

bool HasNameMatcher::matchesNodeFullSlow(const NamedDecl &Node) const {
const StringRef Pattern = Name;

if (Pattern.startswith("::"))
return FullName == Pattern;
const bool SkipUnwrittenCases[] = {false, true};
for (bool SkipUnwritten : SkipUnwrittenCases) {
llvm::SmallString<128> NodeName = StringRef("::");
llvm::raw_svector_ostream OS(NodeName);

if (SkipUnwritten) {
PrintingPolicy Policy = Node.getASTContext().getPrintingPolicy();
Policy.SuppressUnwrittenScope = true;
Node.printQualifiedName(OS, Policy);
} else {
Node.printQualifiedName(OS);
}

const StringRef FullName = OS.str();

return FullName.endswith(Pattern) &&
FullName.drop_back(Pattern.size()).endswith("::");
if (Pattern.startswith("::")) {
if (FullName == Pattern)
return true;
} else if (FullName.endswith(Pattern) &&
FullName.drop_back(Pattern.size()).endswith("::")) {
return true;
}
}

return false;
}

bool HasNameMatcher::matchesNode(const NamedDecl &Node) const {
// FIXME: There is still room for improvement, but it would require copying a
// lot of the logic from NamedDecl::printQualifiedName(). The benchmarks do
// not show like that extra complexity is needed right now.
assert(matchesNodeFullFast(Node) == matchesNodeFullSlow(Node));
if (UseUnqualifiedMatch) {
assert(matchesNodeUnqualified(Node) == matchesNodeFull(Node));
assert(matchesNodeUnqualified(Node) == matchesNodeFullFast(Node));
return matchesNodeUnqualified(Node);
}
return matchesNodeFull(Node);
return matchesNodeFullFast(Node);
}

} // end namespace internal
Expand Down
46 changes: 46 additions & 0 deletions clang/unittests/ASTMatchers/ASTMatchersTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2827,6 +2827,52 @@ TEST(Matcher, HasNameSupportsOuterClasses) {
recordDecl(hasName("A+B::C"))));
}

TEST(Matcher, HasNameSupportsInlinedNamespaces) {
std::string code = "namespace a { inline namespace b { class C; } }";
EXPECT_TRUE(matches(code, recordDecl(hasName("a::b::C"))));
EXPECT_TRUE(matches(code, recordDecl(hasName("a::C"))));
EXPECT_TRUE(matches(code, recordDecl(hasName("::a::b::C"))));
EXPECT_TRUE(matches(code, recordDecl(hasName("::a::C"))));
}

TEST(Matcher, HasNameSupportsAnonymousNamespaces) {
std::string code = "namespace a { namespace { class C; } }";
EXPECT_TRUE(
matches(code, recordDecl(hasName("a::(anonymous namespace)::C"))));
EXPECT_TRUE(matches(code, recordDecl(hasName("a::C"))));
EXPECT_TRUE(
matches(code, recordDecl(hasName("::a::(anonymous namespace)::C"))));
EXPECT_TRUE(matches(code, recordDecl(hasName("::a::C"))));
}

TEST(Matcher, HasNameSupportsAnonymousOuterClasses) {
EXPECT_TRUE(matches("class A { class { class C; } x; };",
recordDecl(hasName("A::(anonymous class)::C"))));
EXPECT_TRUE(matches("class A { class { class C; } x; };",
recordDecl(hasName("::A::(anonymous class)::C"))));
EXPECT_FALSE(matches("class A { class { class C; } x; };",
recordDecl(hasName("::A::C"))));
EXPECT_TRUE(matches("class A { struct { class C; } x; };",
recordDecl(hasName("A::(anonymous struct)::C"))));
EXPECT_TRUE(matches("class A { struct { class C; } x; };",
recordDecl(hasName("::A::(anonymous struct)::C"))));
EXPECT_FALSE(matches("class A { struct { class C; } x; };",
recordDecl(hasName("::A::C"))));
}

TEST(Matcher, HasNameSupportsFunctionScope) {
std::string code =
"namespace a { void F(int a) { struct S { int m; }; int i; } }";
EXPECT_TRUE(matches(code, varDecl(hasName("i"))));
EXPECT_FALSE(matches(code, varDecl(hasName("F()::i"))));

EXPECT_TRUE(matches(code, fieldDecl(hasName("m"))));
EXPECT_TRUE(matches(code, fieldDecl(hasName("S::m"))));
EXPECT_TRUE(matches(code, fieldDecl(hasName("F(int)::S::m"))));
EXPECT_TRUE(matches(code, fieldDecl(hasName("a::F(int)::S::m"))));
EXPECT_TRUE(matches(code, fieldDecl(hasName("::a::F(int)::S::m"))));
}

TEST(Matcher, IsDefinition) {
DeclarationMatcher DefinitionOfClassA =
recordDecl(hasName("A"), isDefinition());
Expand Down

0 comments on commit 8e566f3

Please sign in to comment.