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

source code of libcxx/test/tools/clang_tidy_checks/uglify_attributes.cpp