| // 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/base_tracing.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; |
| } |