1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // This implements a Clang tool to annotate methods with tracing. It should be
6 // run using the tools/clang/scripts/run_tool.py helper as described in
7 // README.md
8 
9 #include <string>
10 #include <vector>
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Basic/SourceManager.h"
14 #include "clang/Frontend/FrontendActions.h"
15 #include "clang/Tooling/CommonOptionsParser.h"
16 #include "clang/Tooling/Refactoring.h"
17 #include "clang/Tooling/Tooling.h"
18 #include "llvm/Support/CommandLine.h"
19 #include "llvm/Support/FormatVariadic.h"
20 
21 using namespace clang::ast_matchers;
22 using clang::tooling::CommonOptionsParser;
23 using clang::tooling::Replacement;
24 using clang::tooling::Replacements;
25 
26 namespace {
27 
28 class FunctionDefCallback : public MatchFinder::MatchCallback {
29  public:
FunctionDefCallback(std::vector<Replacement> * replacements)30   explicit FunctionDefCallback(std::vector<Replacement>* replacements)
31       : replacements_(replacements) {}
32 
33   void run(const MatchFinder::MatchResult& result) override;
34 
35  private:
36   std::vector<Replacement>* const replacements_;
37 };
38 
39 class TraceAnnotator {
40  public:
TraceAnnotator(std::vector<Replacement> * replacements)41   explicit TraceAnnotator(std::vector<Replacement>* replacements)
42       : function_def_callback_(replacements) {}
43 
44   void SetupMatchers(MatchFinder* match_finder);
45 
46  private:
47   FunctionDefCallback function_def_callback_;
48 };
49 
50 // Given:
51 //   template <typename T, typename T2> void foo(T t, T2 t2) {};  // N1 and N4
52 //   template <typename T2> void foo<int, T2>(int t, T2 t) {};    // N2
53 //   template <> void foo<int, char>(int t, char t2) {};          // N3
54 //   void foo() {
55 //     // This creates implicit template specialization (N4) out of the
56 //     // explicit template definition (N1).
57 //     foo<bool, double>(true, 1.23);
58 //   }
59 // with the following AST nodes:
60 //   FunctionTemplateDecl foo
61 //   |-FunctionDecl 0x191da68 foo 'void (T, T2)'         // N1
62 //   `-FunctionDecl 0x194bf08 foo 'void (bool, double)'  // N4
63 //   FunctionTemplateDecl foo
64 //   `-FunctionDecl foo 'void (int, T2)'                 // N2
65 //   FunctionDecl foo 'void (int, char)'                 // N3
66 //
67 // Matches AST node N4, but not AST nodes N1, N2 nor N3.
AST_MATCHER(clang::FunctionDecl,isImplicitFunctionTemplateSpecialization)68 AST_MATCHER(clang::FunctionDecl, isImplicitFunctionTemplateSpecialization) {
69   switch (Node.getTemplateSpecializationKind()) {
70     case clang::TSK_ImplicitInstantiation:
71       return true;
72     case clang::TSK_Undeclared:
73     case clang::TSK_ExplicitSpecialization:
74     case clang::TSK_ExplicitInstantiationDeclaration:
75     case clang::TSK_ExplicitInstantiationDefinition:
76       return false;
77   }
78 }
79 
AST_POLYMORPHIC_MATCHER(isInMacroLocation,AST_POLYMORPHIC_SUPPORTED_TYPES (clang::Decl,clang::Stmt,clang::TypeLoc))80 AST_POLYMORPHIC_MATCHER(isInMacroLocation,
81                         AST_POLYMORPHIC_SUPPORTED_TYPES(clang::Decl,
82                                                         clang::Stmt,
83                                                         clang::TypeLoc)) {
84   return Node.getBeginLoc().isMacroID();
85 }
86 
SetupMatchers(MatchFinder * match_finder)87 void TraceAnnotator::SetupMatchers(MatchFinder* match_finder) {
88   const clang::ast_matchers::DeclarationMatcher function_call =
89       functionDecl(
90           has(compoundStmt().bind("function body")),
91           /* Avoid matching the following cases: */
92           unless(anyOf(
93               /* Do not match implicit function template specializations to
94                  avoid conflicting edits. */
95               isImplicitFunctionTemplateSpecialization(),
96               /* Do not match constexpr functions. */
97               isConstexpr(), isDefaulted(),
98               /* Do not match ctor/dtor. */
99               cxxConstructorDecl(), cxxDestructorDecl(),
100               /* Tracing macros can be tricky (e.g., QuicUint128Impl comparison
101                  operators). */
102               isInMacroLocation(), has(compoundStmt(isInMacroLocation())),
103               /* Do not trace lambdas (no name, possbly tracking more parameters
104                  than intended because of [&]). */
105               hasParent(cxxRecordDecl(isLambda())))))
106           .bind("function");
107   match_finder->addMatcher(function_call, &function_def_callback_);
108 }
109 
110 // Returns a string containing the qualified name of the function. Does not
111 // output template parameters of the function or in case of methods of the
112 // associated class (as opposed to |function->getQualifiedNameAsString|).
getFunctionName(const clang::FunctionDecl * function)113 std::string getFunctionName(const clang::FunctionDecl* function) {
114   std::string qualified_name;
115   // Add namespace(s) to the name.
116   if (auto* name_space = llvm::dyn_cast<clang::NamespaceDecl>(
117           function->getEnclosingNamespaceContext())) {
118     qualified_name += name_space->getQualifiedNameAsString();
119     qualified_name += "::";
120   }
121   // If the function is a method, add class name (without templates).
122   if (auto* method = llvm::dyn_cast<clang::CXXMethodDecl>(function)) {
123     qualified_name += method->getParent()->getNameAsString();
124     qualified_name += "::";
125   }
126   // Add function name (without templates).
127   qualified_name += function->getNameAsString();
128   return qualified_name;
129 }
130 
run(const MatchFinder::MatchResult & result)131 void FunctionDefCallback::run(const MatchFinder::MatchResult& result) {
132   const clang::FunctionDecl* function =
133       result.Nodes.getNodeAs<clang::FunctionDecl>("function");
134   // Using this instead of |function->getBody| prevents conflicts with parameter
135   // names in headers and implementations.
136   const clang::CompoundStmt* function_body =
137       result.Nodes.getNodeAs<clang::CompoundStmt>("function body");
138   clang::CharSourceRange range =
139       clang::CharSourceRange::getTokenRange(function_body->getBeginLoc());
140 
141   const char kReplacementTextTemplate[] = R"( TRACE_EVENT0("test", "{0}"); )";
142   std::string function_name = getFunctionName(function);
143   std::string replacement_text =
144       llvm::formatv(kReplacementTextTemplate, function_name).str();
145 
146   const char kAnnotationTemplate[] = " { {0}";
147   std::string annotation =
148       llvm::formatv(kAnnotationTemplate, replacement_text).str();
149 
150   replacements_->push_back(
151       Replacement(*result.SourceManager, range, annotation));
152 }
153 
154 }  // namespace
155 
156 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
157 
main(int argc,const char * argv[])158 int main(int argc, const char* argv[]) {
159   llvm::cl::OptionCategory category("TraceAnnotator Tool");
160   CommonOptionsParser options(argc, argv, category);
161   clang::tooling::ClangTool tool(options.getCompilations(),
162                                  options.getSourcePathList());
163 
164   std::vector<Replacement> replacements;
165   TraceAnnotator converter(&replacements);
166   MatchFinder match_finder;
167   converter.SetupMatchers(&match_finder);
168 
169   std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
170       clang::tooling::newFrontendActionFactory(&match_finder);
171   int result = tool.run(frontend_factory.get());
172   if (result != 0)
173     return result;
174 
175   if (replacements.empty())
176     return 0;
177 
178   // Each replacement line should have the following format:
179   // r:<file path>:<offset>:<length>:<replacement text>
180   // Only the <replacement text> field can contain embedded ":" characters.
181   // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
182   // serialization and then use clang-apply-replacements, but that would require
183   // copying and pasting a larger amount of boilerplate for all Chrome clang
184   // tools.
185 
186   // Keep a set of files where we have already added base_tracing include.
187   std::set<std::string> include_added_to;
188 
189   llvm::outs() << "==== BEGIN EDITS ====\n";
190   for (const auto& r : replacements) {
191     // Add base_tracing import if necessary.
192     if (include_added_to.find(r.getFilePath().str()) ==
193         include_added_to.end()) {
194       include_added_to.insert(r.getFilePath().str());
195       // Add also copyright so that |test-expected.cc| passes presubmit.
196       llvm::outs() << "include-user-header:::" << r.getFilePath()
197                    << ":::-1:::-1:::base/trace_event/base_tracing.h"
198                    << "\n";
199     }
200     // Add the actual replacement.
201     llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset()
202                  << ":::" << r.getLength() << ":::" << r.getReplacementText()
203                  << "\n";
204   }
205   llvm::outs() << "==== END EDITS ====\n";
206 
207   return 0;
208 }
209