1 | //===--- RedundantInlineSpecifierCheck.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 "RedundantInlineSpecifierCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/Decl.h" |
12 | #include "clang/AST/DeclCXX.h" |
13 | #include "clang/AST/DeclTemplate.h" |
14 | #include "clang/AST/ExprCXX.h" |
15 | #include "clang/ASTMatchers/ASTMatchers.h" |
16 | #include "clang/Basic/Diagnostic.h" |
17 | #include "clang/Basic/SourceLocation.h" |
18 | #include "clang/Basic/SourceManager.h" |
19 | #include "clang/Lex/Token.h" |
20 | |
21 | #include "../utils/LexerUtils.h" |
22 | |
23 | using namespace clang::ast_matchers; |
24 | |
25 | namespace clang::tidy::readability { |
26 | |
27 | namespace { |
28 | AST_POLYMORPHIC_MATCHER(isInlineSpecified, |
29 | AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, |
30 | VarDecl)) { |
31 | if (const auto *FD = dyn_cast<FunctionDecl>(&Node)) |
32 | return FD->isInlineSpecified(); |
33 | if (const auto *VD = dyn_cast<VarDecl>(&Node)) |
34 | return VD->isInlineSpecified(); |
35 | llvm_unreachable("Not a valid polymorphic type" ); |
36 | } |
37 | |
38 | AST_POLYMORPHIC_MATCHER_P(isInternalLinkage, |
39 | AST_POLYMORPHIC_SUPPORTED_TYPES(FunctionDecl, |
40 | VarDecl), |
41 | bool, strictMode) { |
42 | if (!strictMode) |
43 | return false; |
44 | if (const auto *FD = dyn_cast<FunctionDecl>(&Node)) |
45 | return FD->getStorageClass() == SC_Static || FD->isInAnonymousNamespace(); |
46 | if (const auto *VD = dyn_cast<VarDecl>(&Node)) |
47 | return VD->isInAnonymousNamespace(); |
48 | llvm_unreachable("Not a valid polymorphic type" ); |
49 | } |
50 | } // namespace |
51 | |
52 | static SourceLocation getInlineTokenLocation(SourceRange RangeLocation, |
53 | const SourceManager &Sources, |
54 | const LangOptions &LangOpts) { |
55 | SourceLocation Loc = RangeLocation.getBegin(); |
56 | if (Loc.isMacroID()) |
57 | return {}; |
58 | |
59 | Token FirstToken; |
60 | Lexer::getRawToken(Loc, Result&: FirstToken, SM: Sources, LangOpts, IgnoreWhiteSpace: true); |
61 | std::optional<Token> CurrentToken = FirstToken; |
62 | while (CurrentToken && CurrentToken->getLocation() < RangeLocation.getEnd() && |
63 | CurrentToken->isNot(K: tok::eof)) { |
64 | if (CurrentToken->is(K: tok::raw_identifier) && |
65 | CurrentToken->getRawIdentifier() == "inline" ) |
66 | return CurrentToken->getLocation(); |
67 | |
68 | CurrentToken = |
69 | Lexer::findNextToken(Loc: CurrentToken->getLocation(), SM: Sources, LangOpts); |
70 | } |
71 | return {}; |
72 | } |
73 | |
74 | void RedundantInlineSpecifierCheck::registerMatchers(MatchFinder *Finder) { |
75 | Finder->addMatcher( |
76 | NodeMatch: functionDecl(isInlineSpecified(), |
77 | anyOf(isConstexpr(), isDeleted(), isDefaulted(), |
78 | isInternalLinkage(strictMode: StrictMode), |
79 | allOf(isDefinition(), hasAncestor(recordDecl())))) |
80 | .bind(ID: "fun_decl" ), |
81 | Action: this); |
82 | |
83 | if (StrictMode) |
84 | Finder->addMatcher( |
85 | NodeMatch: functionTemplateDecl( |
86 | has(functionDecl(allOf(isInlineSpecified(), isDefinition())))) |
87 | .bind(ID: "templ_decl" ), |
88 | Action: this); |
89 | |
90 | if (getLangOpts().CPlusPlus17) { |
91 | const auto IsPartOfRecordDecl = hasAncestor(recordDecl()); |
92 | Finder->addMatcher( |
93 | NodeMatch: varDecl( |
94 | isInlineSpecified(), |
95 | anyOf(allOf(isInternalLinkage(strictMode: StrictMode), |
96 | unless(allOf(hasInitializer(InnerMatcher: expr()), IsPartOfRecordDecl, |
97 | isStaticStorageClass()))), |
98 | allOf(isConstexpr(), IsPartOfRecordDecl))) |
99 | .bind(ID: "var_decl" ), |
100 | Action: this); |
101 | } |
102 | } |
103 | |
104 | template <typename T> |
105 | void RedundantInlineSpecifierCheck::handleMatchedDecl( |
106 | const T *MatchedDecl, const SourceManager &Sources, |
107 | const MatchFinder::MatchResult &Result, StringRef Message) { |
108 | SourceLocation Loc = getInlineTokenLocation( |
109 | MatchedDecl->getSourceRange(), Sources, Result.Context->getLangOpts()); |
110 | if (Loc.isValid()) |
111 | diag(Loc, Description: Message) << MatchedDecl << FixItHint::CreateRemoval(RemoveRange: Loc); |
112 | } |
113 | |
114 | void RedundantInlineSpecifierCheck::check( |
115 | const MatchFinder::MatchResult &Result) { |
116 | const SourceManager &Sources = *Result.SourceManager; |
117 | |
118 | if (const auto *MatchedDecl = |
119 | Result.Nodes.getNodeAs<FunctionDecl>(ID: "fun_decl" )) { |
120 | handleMatchedDecl( |
121 | MatchedDecl, Sources, Result, |
122 | Message: "function %0 has inline specifier but is implicitly inlined" ); |
123 | } else if (const auto *MatchedDecl = |
124 | Result.Nodes.getNodeAs<VarDecl>(ID: "var_decl" )) { |
125 | handleMatchedDecl( |
126 | MatchedDecl, Sources, Result, |
127 | Message: "variable %0 has inline specifier but is implicitly inlined" ); |
128 | } else if (const auto *MatchedDecl = |
129 | Result.Nodes.getNodeAs<FunctionTemplateDecl>(ID: "templ_decl" )) { |
130 | handleMatchedDecl( |
131 | MatchedDecl, Sources, Result, |
132 | Message: "function %0 has inline specifier but is implicitly inlined" ); |
133 | } |
134 | } |
135 | |
136 | } // namespace clang::tidy::readability |
137 | |