blob: bc4970825b4ca977e990ff23b908ad1664f644fd [file] [log] [blame]
//===--- DontModifyStdNamespaceCheck.cpp - clang-tidy----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "DontModifyStdNamespaceCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchersInternal.h"
using namespace clang;
using namespace clang::ast_matchers;
namespace {
AST_POLYMORPHIC_MATCHER_P(
hasAnyTemplateArgumentIncludingPack,
AST_POLYMORPHIC_SUPPORTED_TYPES(ClassTemplateSpecializationDecl,
TemplateSpecializationType, FunctionDecl),
clang::ast_matchers::internal::Matcher<TemplateArgument>, InnerMatcher) {
ArrayRef<TemplateArgument> Args =
clang::ast_matchers::internal::getTemplateSpecializationArgs(Node);
for (const auto &Arg : Args) {
if (Arg.getKind() != TemplateArgument::Pack)
continue;
ArrayRef<TemplateArgument> PackArgs = Arg.getPackAsArray();
if (matchesFirstInRange(InnerMatcher, PackArgs.begin(), PackArgs.end(),
Finder, Builder) != PackArgs.end())
return true;
}
return matchesFirstInRange(InnerMatcher, Args.begin(), Args.end(), Finder,
Builder) != Args.end();
}
} // namespace
namespace clang::tidy::cert {
void DontModifyStdNamespaceCheck::registerMatchers(MatchFinder *Finder) {
auto HasStdParent =
hasDeclContext(namespaceDecl(hasAnyName("std", "posix"),
unless(hasParent(namespaceDecl())))
.bind("nmspc"));
auto UserDefinedType = qualType(
hasUnqualifiedDesugaredType(tagType(unless(hasDeclaration(tagDecl(
hasAncestor(namespaceDecl(hasAnyName("std", "posix"),
unless(hasParent(namespaceDecl()))))))))));
auto HasNoProgramDefinedTemplateArgument = unless(
hasAnyTemplateArgumentIncludingPack(refersToType(UserDefinedType)));
auto InsideStdClassOrClassTemplateSpecialization = hasDeclContext(
anyOf(cxxRecordDecl(HasStdParent),
classTemplateSpecializationDecl(
HasStdParent, HasNoProgramDefinedTemplateArgument)));
// Try to follow exactly CERT rule DCL58-CPP (this text is taken from C++
// standard into the CERT rule):
// "
// 1 The behavior of a C++ program is undefined if it adds declarations or
// definitions to namespace std or to a namespace within namespace std unless
// otherwise specified. A program may add a template specialization for any
// standard library template to namespace std only if the declaration depends
// on a user-defined type and the specialization meets the standard library
// requirements for the original template and is not explicitly prohibited. 2
// The behavior of a C++ program is undefined if it declares — an explicit
// specialization of any member function of a standard library class template,
// or — an explicit specialization of any member function template of a
// standard library class or class template, or — an explicit or partial
// specialization of any member class template of a standard library class or
// class template.
// "
// The "standard library requirements" and explicit prohibition are not
// checked.
auto BadNonTemplateSpecializationDecl =
decl(unless(anyOf(functionDecl(isExplicitTemplateSpecialization()),
varDecl(isExplicitTemplateSpecialization()),
cxxRecordDecl(isExplicitTemplateSpecialization()))),
HasStdParent);
auto BadClassTemplateSpec = classTemplateSpecializationDecl(
HasNoProgramDefinedTemplateArgument, HasStdParent);
auto BadInnerClassTemplateSpec = classTemplateSpecializationDecl(
InsideStdClassOrClassTemplateSpecialization);
auto BadFunctionTemplateSpec =
functionDecl(unless(cxxMethodDecl()), isExplicitTemplateSpecialization(),
HasNoProgramDefinedTemplateArgument, HasStdParent);
auto BadMemberFunctionSpec =
cxxMethodDecl(isExplicitTemplateSpecialization(),
InsideStdClassOrClassTemplateSpecialization);
Finder->addMatcher(decl(anyOf(BadNonTemplateSpecializationDecl,
BadClassTemplateSpec, BadInnerClassTemplateSpec,
BadFunctionTemplateSpec, BadMemberFunctionSpec),
unless(isExpansionInSystemHeader()))
.bind("decl"),
this);
}
} // namespace clang::tidy::cert
static const NamespaceDecl *getTopLevelLexicalNamespaceDecl(const Decl *D) {
const NamespaceDecl *LastNS = nullptr;
while (D) {
if (const auto *NS = dyn_cast<NamespaceDecl>(D))
LastNS = NS;
D = dyn_cast_or_null<Decl>(D->getLexicalDeclContext());
}
return LastNS;
}
void clang::tidy::cert::DontModifyStdNamespaceCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *D = Result.Nodes.getNodeAs<Decl>("decl");
const auto *NS = Result.Nodes.getNodeAs<NamespaceDecl>("nmspc");
if (!D || !NS)
return;
diag(D->getLocation(),
"modification of %0 namespace can result in undefined behavior")
<< NS;
// 'NS' is not always the namespace declaration that lexically contains 'D',
// try to find such a namespace.
if (const NamespaceDecl *LexNS = getTopLevelLexicalNamespaceDecl(D)) {
assert(NS->getCanonicalDecl() == LexNS->getCanonicalDecl() &&
"Mismatch in found namespace");
diag(LexNS->getLocation(), "%0 namespace opened here", DiagnosticIDs::Note)
<< LexNS;
}
}