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