xref: /llvm-project/clang-tools-extra/clang-tidy/llvmlibc/ImplementationInNamespaceCheck.cpp (revision a074f8869563cb5b296732e0e8af46dcdc286ae5)
1 //===--- ImplementationInNamespaceCheck.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 "ImplementationInNamespaceCheck.h"
10 #include "NamespaceConstants.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang::tidy::llvm_libc {
17 
18 void ImplementationInNamespaceCheck::registerMatchers(MatchFinder *Finder) {
19   Finder->addMatcher(
20       translationUnitDecl(
21           forEach(decl(isExpansionInMainFile(), unless(linkageSpecDecl()),
22                        // anonymous namespaces generate usingDirective
23                        unless(usingDirectiveDecl(isImplicit())))
24                       .bind("child_of_translation_unit"))),
25       this);
26 }
27 
28 void ImplementationInNamespaceCheck::check(
29     const MatchFinder::MatchResult &Result) {
30   const auto *MatchedDecl =
31       Result.Nodes.getNodeAs<Decl>("child_of_translation_unit");
32   const auto *NS = dyn_cast<NamespaceDecl>(MatchedDecl);
33 
34   // LLVM libc declarations should be inside of a non-anonymous namespace.
35   if (NS == nullptr || NS->isAnonymousNamespace()) {
36     diag(MatchedDecl->getLocation(),
37          "declaration must be enclosed within the '%0' namespace")
38         << RequiredNamespaceDeclMacroName;
39     return;
40   }
41 
42   // Enforce that the namespace is the result of macro expansion
43   if (Result.SourceManager->isMacroBodyExpansion(NS->getLocation()) == false) {
44     diag(NS->getLocation(), "the outermost namespace should be the '%0' macro")
45         << RequiredNamespaceDeclMacroName;
46     return;
47   }
48 
49   // We want the macro to have [[gnu::visibility("hidden")]] as a prefix, but
50   // visibility is just an attribute in the AST construct, so we check that
51   // instead.
52   if (NS->getVisibility() != Visibility::HiddenVisibility) {
53     diag(NS->getLocation(), "the '%0' macro should start with '%1'")
54         << RequiredNamespaceDeclMacroName << RequiredNamespaceDeclStart;
55     return;
56   }
57 
58   // Lastly, make sure the namespace name actually has the __llvm_libc prefix
59   if (NS->getName().starts_with(RequiredNamespaceRefStart) == false) {
60     diag(NS->getLocation(), "the '%0' macro expansion should start with '%1'")
61         << RequiredNamespaceDeclMacroName << RequiredNamespaceRefStart;
62     return;
63   }
64 }
65 
66 } // namespace clang::tidy::llvm_libc
67