|
9 | 9 | #include "DontModifyStdNamespaceCheck.h"
|
10 | 10 | #include "clang/AST/ASTContext.h"
|
11 | 11 | #include "clang/ASTMatchers/ASTMatchFinder.h"
|
| 12 | +#include "clang/ASTMatchers/ASTMatchersInternal.h" |
12 | 13 |
|
| 14 | +using namespace clang; |
13 | 15 | using namespace clang::ast_matchers;
|
14 | 16 |
|
| 17 | +namespace { |
| 18 | + |
| 19 | +AST_POLYMORPHIC_MATCHER_P( |
| 20 | + hasAnyTemplateArgumentIncludingPack, |
| 21 | + AST_POLYMORPHIC_SUPPORTED_TYPES(ClassTemplateSpecializationDecl, |
| 22 | + TemplateSpecializationType, FunctionDecl), |
| 23 | + clang::ast_matchers::internal::Matcher<TemplateArgument>, InnerMatcher) { |
| 24 | + ArrayRef<TemplateArgument> Args = |
| 25 | + clang::ast_matchers::internal::getTemplateSpecializationArgs(Node); |
| 26 | + for (const auto &Arg : Args) { |
| 27 | + if (Arg.getKind() != TemplateArgument::Pack) |
| 28 | + continue; |
| 29 | + ArrayRef<TemplateArgument> PackArgs = Arg.getPackAsArray(); |
| 30 | + if (matchesFirstInRange(InnerMatcher, PackArgs.begin(), PackArgs.end(), |
| 31 | + Finder, Builder) != PackArgs.end()) |
| 32 | + return true; |
| 33 | + } |
| 34 | + return matchesFirstInRange(InnerMatcher, Args.begin(), Args.end(), Finder, |
| 35 | + Builder) != Args.end(); |
| 36 | +} |
| 37 | + |
| 38 | +} // namespace |
| 39 | + |
15 | 40 | namespace clang {
|
16 | 41 | namespace tidy {
|
17 | 42 | namespace cert {
|
18 | 43 |
|
19 | 44 | void DontModifyStdNamespaceCheck::registerMatchers(MatchFinder *Finder) {
|
20 |
| - Finder->addMatcher( |
21 |
| - namespaceDecl(unless(isExpansionInSystemHeader()), |
22 |
| - hasAnyName("std", "posix"), |
23 |
| - has(decl(unless(anyOf( |
24 |
| - functionDecl(isExplicitTemplateSpecialization()), |
25 |
| - cxxRecordDecl(isExplicitTemplateSpecialization())))))) |
26 |
| - .bind("nmspc"), |
27 |
| - this); |
28 |
| -} |
| 45 | + auto HasStdParent = |
| 46 | + hasDeclContext(namespaceDecl(hasAnyName("std", "posix"), |
| 47 | + unless(hasParent(namespaceDecl()))) |
| 48 | + .bind("nmspc")); |
| 49 | + auto UserDefinedType = qualType( |
| 50 | + hasUnqualifiedDesugaredType(tagType(unless(hasDeclaration(tagDecl( |
| 51 | + hasAncestor(namespaceDecl(hasAnyName("std", "posix"), |
| 52 | + unless(hasParent(namespaceDecl())))))))))); |
| 53 | + auto HasNoProgramDefinedTemplateArgument = unless( |
| 54 | + hasAnyTemplateArgumentIncludingPack(refersToType(UserDefinedType))); |
| 55 | + auto InsideStdClassOrClassTemplateSpecialization = hasDeclContext( |
| 56 | + anyOf(cxxRecordDecl(HasStdParent), |
| 57 | + classTemplateSpecializationDecl( |
| 58 | + HasStdParent, HasNoProgramDefinedTemplateArgument))); |
29 | 59 |
|
30 |
| -void DontModifyStdNamespaceCheck::check( |
31 |
| - const MatchFinder::MatchResult &Result) { |
32 |
| - const auto *N = Result.Nodes.getNodeAs<NamespaceDecl>("nmspc"); |
| 60 | + // Try to follow exactly CERT rule DCL58-CPP (this text is taken from C++ |
| 61 | + // standard into the CERT rule): |
| 62 | + // " |
| 63 | + // 1 The behavior of a C++ program is undefined if it adds declarations or |
| 64 | + // definitions to namespace std or to a namespace within namespace std unless |
| 65 | + // otherwise specified. A program may add a template specialization for any |
| 66 | + // standard library template to namespace std only if the declaration depends |
| 67 | + // on a user-defined type and the specialization meets the standard library |
| 68 | + // requirements for the original template and is not explicitly prohibited. 2 |
| 69 | + // The behavior of a C++ program is undefined if it declares — an explicit |
| 70 | + // specialization of any member function of a standard library class template, |
| 71 | + // or — an explicit specialization of any member function template of a |
| 72 | + // standard library class or class template, or — an explicit or partial |
| 73 | + // specialization of any member class template of a standard library class or |
| 74 | + // class template. |
| 75 | + // " |
| 76 | + // The "standard library requirements" and explicit prohibition are not |
| 77 | + // checked. |
33 | 78 |
|
34 |
| - // Only consider top level namespaces. |
35 |
| - if (N->getParent() != Result.Context->getTranslationUnitDecl()) |
36 |
| - return; |
| 79 | + auto BadNonTemplateSpecializationDecl = |
| 80 | + decl(unless(anyOf(functionDecl(isExplicitTemplateSpecialization()), |
| 81 | + varDecl(isExplicitTemplateSpecialization()), |
| 82 | + cxxRecordDecl(isExplicitTemplateSpecialization()))), |
| 83 | + HasStdParent); |
| 84 | + auto BadClassTemplateSpec = classTemplateSpecializationDecl( |
| 85 | + HasNoProgramDefinedTemplateArgument, HasStdParent); |
| 86 | + auto BadInnerClassTemplateSpec = classTemplateSpecializationDecl( |
| 87 | + InsideStdClassOrClassTemplateSpecialization); |
| 88 | + auto BadFunctionTemplateSpec = |
| 89 | + functionDecl(unless(cxxMethodDecl()), isExplicitTemplateSpecialization(), |
| 90 | + HasNoProgramDefinedTemplateArgument, HasStdParent); |
| 91 | + auto BadMemberFunctionSpec = |
| 92 | + cxxMethodDecl(isExplicitTemplateSpecialization(), |
| 93 | + InsideStdClassOrClassTemplateSpecialization); |
37 | 94 |
|
38 |
| - diag(N->getLocation(), |
39 |
| - "modification of %0 namespace can result in undefined behavior") |
40 |
| - << N; |
| 95 | + Finder->addMatcher(decl(anyOf(BadNonTemplateSpecializationDecl, |
| 96 | + BadClassTemplateSpec, BadInnerClassTemplateSpec, |
| 97 | + BadFunctionTemplateSpec, BadMemberFunctionSpec), |
| 98 | + unless(isExpansionInSystemHeader())) |
| 99 | + .bind("decl"), |
| 100 | + this); |
41 | 101 | }
|
42 |
| - |
43 | 102 | } // namespace cert
|
44 | 103 | } // namespace tidy
|
45 | 104 | } // namespace clang
|
| 105 | + |
| 106 | +static const NamespaceDecl *getTopLevelLexicalNamespaceDecl(const Decl *D) { |
| 107 | + const NamespaceDecl *LastNS = nullptr; |
| 108 | + while (D) { |
| 109 | + if (const auto *NS = dyn_cast<NamespaceDecl>(D)) |
| 110 | + LastNS = NS; |
| 111 | + D = dyn_cast_or_null<Decl>(D->getLexicalDeclContext()); |
| 112 | + } |
| 113 | + return LastNS; |
| 114 | +} |
| 115 | + |
| 116 | +void clang::tidy::cert::DontModifyStdNamespaceCheck::check( |
| 117 | + const MatchFinder::MatchResult &Result) { |
| 118 | + const auto *D = Result.Nodes.getNodeAs<Decl>("decl"); |
| 119 | + const auto *NS = Result.Nodes.getNodeAs<NamespaceDecl>("nmspc"); |
| 120 | + if (!D || !NS) |
| 121 | + return; |
| 122 | + |
| 123 | + diag(D->getLocation(), |
| 124 | + "modification of %0 namespace can result in undefined behavior") |
| 125 | + << NS; |
| 126 | + // 'NS' is not always the namespace declaration that lexically contains 'D', |
| 127 | + // try to find such a namespace. |
| 128 | + if (const NamespaceDecl *LexNS = getTopLevelLexicalNamespaceDecl(D)) { |
| 129 | + assert(NS->getCanonicalDecl() == LexNS->getCanonicalDecl() && |
| 130 | + "Mismatch in found namespace"); |
| 131 | + diag(LexNS->getLocation(), "%0 namespace opened here", DiagnosticIDs::Note) |
| 132 | + << LexNS; |
| 133 | + } |
| 134 | +} |
0 commit comments