1 //===--- DontModifyStdNamespaceCheck.cpp - clang-tidy----------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "DontModifyStdNamespaceCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/ASTMatchers/ASTMatchersInternal.h"
13 
14 using namespace clang;
15 using namespace clang::ast_matchers;
16 
17 namespace {
18 
AST_POLYMORPHIC_MATCHER_P(hasAnyTemplateArgumentIncludingPack,AST_POLYMORPHIC_SUPPORTED_TYPES (ClassTemplateSpecializationDecl,TemplateSpecializationType,FunctionDecl),clang::ast_matchers::internal::Matcher<TemplateArgument>,InnerMatcher)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 
40 namespace clang {
41 namespace tidy {
42 namespace cert {
43 
registerMatchers(MatchFinder * Finder)44 void DontModifyStdNamespaceCheck::registerMatchers(MatchFinder *Finder) {
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)));
59 
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.
78 
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);
94 
95   Finder->addMatcher(decl(anyOf(BadNonTemplateSpecializationDecl,
96                                 BadClassTemplateSpec, BadInnerClassTemplateSpec,
97                                 BadFunctionTemplateSpec, BadMemberFunctionSpec),
98                           unless(isExpansionInSystemHeader()))
99                          .bind("decl"),
100                      this);
101 }
102 } // namespace cert
103 } // namespace tidy
104 } // namespace clang
105 
getTopLevelLexicalNamespaceDecl(const Decl * D)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 
check(const MatchFinder::MatchResult & Result)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 }
135