File: TraceAnnotator.cpp

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (213 lines) | stat: -rw-r--r-- 8,303 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This implements a Clang tool to annotate methods with tracing. It should be
// run using the tools/clang/scripts/run_tool.py helper as described in
// README.md

#include <string>
#include <vector>
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FormatVariadic.h"

using namespace clang::ast_matchers;
using clang::tooling::CommonOptionsParser;
using clang::tooling::Replacement;
using clang::tooling::Replacements;

namespace {

class FunctionDefCallback : public MatchFinder::MatchCallback {
 public:
  explicit FunctionDefCallback(std::vector<Replacement>* replacements)
      : replacements_(replacements) {}

  void run(const MatchFinder::MatchResult& result) override;

 private:
  std::vector<Replacement>* const replacements_;
};

class TraceAnnotator {
 public:
  explicit TraceAnnotator(std::vector<Replacement>* replacements)
      : function_def_callback_(replacements) {}

  void SetupMatchers(MatchFinder* match_finder);

 private:
  FunctionDefCallback function_def_callback_;
};

// Given:
//   template <typename T, typename T2> void foo(T t, T2 t2) {};  // N1 and N4
//   template <typename T2> void foo<int, T2>(int t, T2 t) {};    // N2
//   template <> void foo<int, char>(int t, char t2) {};          // N3
//   void foo() {
//     // This creates implicit template specialization (N4) out of the
//     // explicit template definition (N1).
//     foo<bool, double>(true, 1.23);
//   }
// with the following AST nodes:
//   FunctionTemplateDecl foo
//   |-FunctionDecl 0x191da68 foo 'void (T, T2)'         // N1
//   `-FunctionDecl 0x194bf08 foo 'void (bool, double)'  // N4
//   FunctionTemplateDecl foo
//   `-FunctionDecl foo 'void (int, T2)'                 // N2
//   FunctionDecl foo 'void (int, char)'                 // N3
//
// Matches AST node N4, but not AST nodes N1, N2 nor N3.
AST_MATCHER(clang::FunctionDecl, isImplicitFunctionTemplateSpecialization) {
  switch (Node.getTemplateSpecializationKind()) {
    case clang::TSK_ImplicitInstantiation:
      return true;
    case clang::TSK_Undeclared:
    case clang::TSK_ExplicitSpecialization:
    case clang::TSK_ExplicitInstantiationDeclaration:
    case clang::TSK_ExplicitInstantiationDefinition:
      return false;
  }
}

AST_POLYMORPHIC_MATCHER(isInMacroLocation,
                        AST_POLYMORPHIC_SUPPORTED_TYPES(clang::Decl,
                                                        clang::Stmt,
                                                        clang::TypeLoc)) {
  return Node.getBeginLoc().isMacroID();
}

void TraceAnnotator::SetupMatchers(MatchFinder* match_finder) {
  const clang::ast_matchers::DeclarationMatcher function_call =
      functionDecl(
          has(compoundStmt().bind("function body")),
          /* Avoid matching the following cases: */
          unless(anyOf(
              /* Do not match implicit function template specializations to
                 avoid conflicting edits. */
              isImplicitFunctionTemplateSpecialization(),
              /* Do not match constexpr functions. */
              isConstexpr(), isDefaulted(),
              /* Do not match ctor/dtor. */
              cxxConstructorDecl(), cxxDestructorDecl(),
              /* Tracing macros can be tricky (e.g., QuicUint128Impl comparison
                 operators). */
              isInMacroLocation(), has(compoundStmt(isInMacroLocation())),
              /* Do not trace lambdas (no name, possbly tracking more parameters
                 than intended because of [&]). */
              hasParent(cxxRecordDecl(isLambda())))))
          .bind("function");
  match_finder->addMatcher(function_call, &function_def_callback_);
}

// Returns a string containing the qualified name of the function. Does not
// output template parameters of the function or in case of methods of the
// associated class (as opposed to |function->getQualifiedNameAsString|).
std::string getFunctionName(const clang::FunctionDecl* function) {
  std::string qualified_name;
  // Add namespace(s) to the name.
  if (auto* name_space = llvm::dyn_cast<clang::NamespaceDecl>(
          function->getEnclosingNamespaceContext())) {
    qualified_name += name_space->getQualifiedNameAsString();
    qualified_name += "::";
  }
  // If the function is a method, add class name (without templates).
  if (auto* method = llvm::dyn_cast<clang::CXXMethodDecl>(function)) {
    qualified_name += method->getParent()->getNameAsString();
    qualified_name += "::";
  }
  // Add function name (without templates).
  qualified_name += function->getNameAsString();
  return qualified_name;
}

void FunctionDefCallback::run(const MatchFinder::MatchResult& result) {
  const clang::FunctionDecl* function =
      result.Nodes.getNodeAs<clang::FunctionDecl>("function");
  // Using this instead of |function->getBody| prevents conflicts with parameter
  // names in headers and implementations.
  const clang::CompoundStmt* function_body =
      result.Nodes.getNodeAs<clang::CompoundStmt>("function body");
  clang::CharSourceRange range =
      clang::CharSourceRange::getTokenRange(function_body->getBeginLoc());

  const char kReplacementTextTemplate[] = R"( TRACE_EVENT0("test", "{0}"); )";
  std::string function_name = getFunctionName(function);
  std::string replacement_text =
      llvm::formatv(kReplacementTextTemplate, function_name).str();

  const char kAnnotationTemplate[] = " { {0}";
  std::string annotation =
      llvm::formatv(kAnnotationTemplate, replacement_text).str();

  replacements_->push_back(
      Replacement(*result.SourceManager, range, annotation));
}

}  // namespace

static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);

int main(int argc, const char* argv[]) {
  llvm::cl::OptionCategory category("TraceAnnotator Tool");
  llvm::Expected<CommonOptionsParser> options =
      CommonOptionsParser::create(argc, argv, category);
  if (!options) {
    llvm::outs() << llvm::toString(options.takeError());
    return 1;
  }
  clang::tooling::ClangTool tool(options->getCompilations(),
                                 options->getSourcePathList());

  std::vector<Replacement> replacements;
  TraceAnnotator converter(&replacements);
  MatchFinder match_finder;
  converter.SetupMatchers(&match_finder);

  std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory =
      clang::tooling::newFrontendActionFactory(&match_finder);
  int result = tool.run(frontend_factory.get());
  if (result != 0)
    return result;

  if (replacements.empty())
    return 0;

  // Each replacement line should have the following format:
  // r:<file path>:<offset>:<length>:<replacement text>
  // Only the <replacement text> field can contain embedded ":" characters.
  // TODO(dcheng): Use a more clever serialization. Ideally we'd use the YAML
  // serialization and then use clang-apply-replacements, but that would require
  // copying and pasting a larger amount of boilerplate for all Chrome clang
  // tools.

  // Keep a set of files where we have already added base_tracing include.
  std::set<std::string> include_added_to;

  llvm::outs() << "==== BEGIN EDITS ====\n";
  for (const auto& r : replacements) {
    // Add base_tracing import if necessary.
    if (include_added_to.find(r.getFilePath().str()) ==
        include_added_to.end()) {
      include_added_to.insert(r.getFilePath().str());
      // Add also copyright so that |test-expected.cc| passes presubmit.
      llvm::outs() << "include-user-header:::" << r.getFilePath()
                   << ":::-1:::-1:::base/trace_event/trace_event.h"
                   << "\n";
    }
    // Add the actual replacement.
    llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset()
                 << ":::" << r.getLength() << ":::" << r.getReplacementText()
                 << "\n";
  }
  llvm::outs() << "==== END EDITS ====\n";

  return 0;
}