xref: /llvm-project/libcxx/test/tools/clang_tidy_checks/uglify_attributes.cpp (revision f69585235ec85d54e0f3fc41b2d5700430907f99)
1 //===----------------------------------------------------------------------===//
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 "clang-tidy/ClangTidyCheck.h"
10 #include "clang-tidy/ClangTidyModuleRegistry.h"
11 
12 #include "uglify_attributes.hpp"
13 #include "utilities.hpp"
14 
15 #include <optional>
16 
17 namespace {
18 
19 // Starting with Clang 17 ToT C++23 support is provided by CPlusPlus23 instead
20 // of C++23 support is provided by CPlusPlus2b. To allow a smooth transition for
21 // libc++ use "reflection" to select the proper member. Since the change
22 // happens in the development cycle it's not possible to use #ifdefs.
23 template <class T>
24 bool CPlusPlus23(const T& lang_opts)
25   requires requires { T::CPlusPlus2b; }
26 {
27   return lang_opts.CPlusPlus2b;
28 }
29 
30 template <class T>
31 bool CPlusPlus23(const T& lang_opts)
32   requires requires { T::CPlusPlus23; }
33 {
34   return lang_opts.CPlusPlus23;
35 }
36 
37 std::vector<const char*> get_standard_attributes(const clang::LangOptions& lang_opts) {
38   std::vector<const char*> attributes;
39 
40   if (lang_opts.CPlusPlus11) {
41     attributes.emplace_back("noreturn");
42     attributes.emplace_back("carries_dependency");
43   }
44 
45   if (lang_opts.CPlusPlus14)
46     attributes.emplace_back("deprecated");
47 
48   if (lang_opts.CPlusPlus17) {
49     attributes.emplace_back("fallthrough");
50     attributes.emplace_back("nodiscard");
51     attributes.emplace_back("maybe_unused");
52   }
53 
54   if (lang_opts.CPlusPlus20) {
55     attributes.emplace_back("likely");
56     attributes.emplace_back("unlikely");
57     attributes.emplace_back("no_unique_address");
58   }
59 
60   if (CPlusPlus23(lang_opts)) {
61     attributes.emplace_back("assume");
62   }
63 
64   return attributes;
65 }
66 
67 AST_MATCHER(clang::Attr, isPretty) {
68   if (Node.isKeywordAttribute() || !Node.getAttrName())
69     return false;
70   if (Node.isCXX11Attribute() && !Node.hasScope()) {
71     if (is_ugly_name(Node.getAttrName()->getName()))
72       return false;
73     return !llvm::is_contained(
74         get_standard_attributes(Finder->getASTContext().getLangOpts()), Node.getAttrName()->getName());
75   }
76   if (Node.hasScope())
77     if (!is_ugly_name(Node.getScopeName()->getName()))
78       return true;
79   return !is_ugly_name(Node.getAttrName()->getName());
80 }
81 
82 std::optional<std::string> getUglyfiedCXX11Attr(const clang::Attr& attr) {
83   // TODO: Don't emit FixItHints for attributes with `using` in them or emit correct fixes.
84 
85   std::string attr_string;
86   if (attr.isClangScope())
87     attr_string += "_Clang::";
88   else if (attr.isGNUScope())
89     attr_string += "__gnu__::";
90 
91   if (!attr.getAttrName()->getName().starts_with("__")) {
92     attr_string += "__";
93     attr_string += attr.getAttrName()->getName();
94     attr_string += "__";
95   } else {
96     attr_string += attr.getAttrName()->getName();
97   }
98   return std::move(attr_string);
99 }
100 
101 std::optional<std::string> getUglyfiedGNUAttr(const clang::Attr& attr) {
102   return "__" + attr.getAttrName()->getName().str() + "__";
103 }
104 
105 std::optional<std::string> getUglified(const clang::Attr& attr) {
106   if (attr.isCXX11Attribute()) {
107     return getUglyfiedCXX11Attr(attr);
108   } else if (attr.isGNUAttribute()) {
109     return getUglyfiedGNUAttr(attr);
110   }
111 
112   return std::nullopt;
113 }
114 } // namespace
115 
116 namespace libcpp {
117 uglify_attributes::uglify_attributes(llvm::StringRef name, clang::tidy::ClangTidyContext* context)
118     : clang::tidy::ClangTidyCheck(name, context) {}
119 
120 void uglify_attributes::registerMatchers(clang::ast_matchers::MatchFinder* finder) {
121   using namespace clang::ast_matchers;
122   finder->addMatcher(attr(isPretty()).bind("normal_attribute"), this);
123 }
124 
125 void uglify_attributes::check(const clang::ast_matchers::MatchFinder::MatchResult& result) {
126   if (const auto* attr = result.Nodes.getNodeAs<clang::Attr>("normal_attribute"); attr != nullptr) {
127     auto diagnostic = diag(attr->getLoc(), "Non-standard attributes should use the _Ugly spelling");
128     auto uglified   = getUglified(*attr);
129     if (uglified.has_value()) {
130       diagnostic << clang::FixItHint::CreateReplacement(attr->getRange(), *uglified);
131     }
132   }
133 }
134 } // namespace libcpp
135