xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp (revision 7fcca112034f55c26e943c13fd0d57adfbe96705)
1 //===--- LambdaFunctionNameCheck.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 "LambdaFunctionNameCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/DeclCXX.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Frontend/CompilerInstance.h"
15 #include "clang/Lex/MacroInfo.h"
16 #include "clang/Lex/Preprocessor.h"
17 
18 using namespace clang::ast_matchers;
19 
20 namespace clang::tidy::bugprone {
21 
22 namespace {
23 
24 static constexpr bool DefaultIgnoreMacros = false;
25 
26 // Keep track of macro expansions that contain both __FILE__ and __LINE__. If
27 // such a macro also uses __func__ or __FUNCTION__, we don't want to issue a
28 // warning because __FILE__ and __LINE__ may be useful even if __func__ or
29 // __FUNCTION__ is not, especially if the macro could be used in the context of
30 // either a function body or a lambda body.
31 class MacroExpansionsWithFileAndLine : public PPCallbacks {
32 public:
MacroExpansionsWithFileAndLine(LambdaFunctionNameCheck::SourceRangeSet * SME)33   explicit MacroExpansionsWithFileAndLine(
34       LambdaFunctionNameCheck::SourceRangeSet *SME)
35       : SuppressMacroExpansions(SME) {}
36 
MacroExpands(const Token & MacroNameTok,const MacroDefinition & MD,SourceRange Range,const MacroArgs * Args)37   void MacroExpands(const Token &MacroNameTok,
38                     const MacroDefinition &MD, SourceRange Range,
39                     const MacroArgs *Args) override {
40     bool HasFile = false;
41     bool HasLine = false;
42     for (const auto& T : MD.getMacroInfo()->tokens()) {
43       if (T.is(tok::identifier)) {
44         StringRef IdentName = T.getIdentifierInfo()->getName();
45         if (IdentName == "__FILE__") {
46           HasFile = true;
47         } else if (IdentName == "__LINE__") {
48           HasLine = true;
49         }
50       }
51     }
52     if (HasFile && HasLine) {
53       SuppressMacroExpansions->insert(Range);
54     }
55   }
56 
57 private:
58   LambdaFunctionNameCheck::SourceRangeSet* SuppressMacroExpansions;
59 };
60 
AST_MATCHER(CXXMethodDecl,isInLambda)61 AST_MATCHER(CXXMethodDecl, isInLambda) { return Node.getParent()->isLambda(); }
62 
63 } // namespace
64 
LambdaFunctionNameCheck(StringRef Name,ClangTidyContext * Context)65 LambdaFunctionNameCheck::LambdaFunctionNameCheck(StringRef Name,
66                                                  ClangTidyContext *Context)
67     : ClangTidyCheck(Name, Context),
68       IgnoreMacros(
69           Options.getLocalOrGlobal("IgnoreMacros", DefaultIgnoreMacros)) {}
70 
storeOptions(ClangTidyOptions::OptionMap & Opts)71 void LambdaFunctionNameCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
72   Options.store(Opts, "IgnoreMacros", IgnoreMacros);
73 }
74 
registerMatchers(MatchFinder * Finder)75 void LambdaFunctionNameCheck::registerMatchers(MatchFinder *Finder) {
76   Finder->addMatcher(
77       cxxMethodDecl(isInLambda(),
78                     hasBody(forEachDescendant(
79                         predefinedExpr(hasAncestor(cxxMethodDecl().bind("fn")))
80                             .bind("E"))),
81                     equalsBoundNode("fn")),
82       this);
83 }
84 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)85 void LambdaFunctionNameCheck::registerPPCallbacks(
86     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
87   PP->addPPCallbacks(std::make_unique<MacroExpansionsWithFileAndLine>(
88       &SuppressMacroExpansions));
89 }
90 
check(const MatchFinder::MatchResult & Result)91 void LambdaFunctionNameCheck::check(const MatchFinder::MatchResult &Result) {
92   const auto *E = Result.Nodes.getNodeAs<PredefinedExpr>("E");
93   if (E->getIdentKind() != PredefinedIdentKind::Func &&
94       E->getIdentKind() != PredefinedIdentKind::Function) {
95     // We don't care about other PredefinedExprs.
96     return;
97   }
98   if (E->getLocation().isMacroID()) {
99     if (IgnoreMacros)
100       return;
101 
102     auto ER =
103         Result.SourceManager->getImmediateExpansionRange(E->getLocation());
104     if (SuppressMacroExpansions.find(ER.getAsRange()) !=
105         SuppressMacroExpansions.end()) {
106       // This is a macro expansion for which we should not warn.
107       return;
108     }
109   }
110 
111   diag(E->getLocation(),
112        "inside a lambda, '%0' expands to the name of the function call "
113        "operator; consider capturing the name of the enclosing function "
114        "explicitly")
115       << PredefinedExpr::getIdentKindName(E->getIdentKind());
116 }
117 
118 } // namespace clang::tidy::bugprone
119