Do not provide diagnostics for cert-dcl58-cpp for compiler generated intrinsic as it will be a false positive. In provided tests compiler generates align_val_t which ends up inside std namespace, resulting in std::align_val_t symbol. This symbol is compiler generated, having no location, causing compiler crash. Also there is no point to notify user about violations which user has no control of. Resolution: Diagnostics suppressed. Co-authored-by: Vladislav Aranov <vladislav.aranov@ericsson.com>
136 lines
5.9 KiB
C++
136 lines
5.9 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 "StdNamespaceModificationCheck.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),
|
|
ast_matchers::internal::Matcher<TemplateArgument>, InnerMatcher) {
|
|
const ArrayRef<TemplateArgument> Args =
|
|
ast_matchers::internal::getTemplateSpecializationArgs(Node);
|
|
for (const auto &Arg : Args) {
|
|
if (Arg.getKind() != TemplateArgument::Pack)
|
|
continue;
|
|
const 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::bugprone {
|
|
|
|
void StdNamespaceModificationCheck::registerMatchers(MatchFinder *Finder) {
|
|
auto HasStdParent =
|
|
hasDeclContext(namespaceDecl(hasAnyName("std", "posix"),
|
|
unless(hasParent(namespaceDecl())))
|
|
.bind("nmspc"));
|
|
auto UserDefinedDecl =
|
|
namedDecl(anyOf(classTemplateDecl(), tagDecl()),
|
|
hasAncestor(namespaceDecl(hasAnyName("std", "posix"),
|
|
unless(hasParent(namespaceDecl())))));
|
|
auto UserDefinedType = qualType(hasUnqualifiedDesugaredType(anyOf(
|
|
tagType(unless(hasDeclaration(UserDefinedDecl))),
|
|
templateSpecializationType(unless(hasDeclaration(UserDefinedDecl))))));
|
|
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))
|
|
.bind("decl"),
|
|
this);
|
|
}
|
|
} // namespace clang::tidy::bugprone
|
|
|
|
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::bugprone::StdNamespaceModificationCheck::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;
|
|
|
|
// Skip compiler-generated implicit declarations (e.g. std::align_val_t).
|
|
if (D->isImplicit())
|
|
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;
|
|
}
|
|
}
|