242 lines · cpp
1//===---- Query.cpp - clang-query query -----------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "Query.h"10#include "QueryParser.h"11#include "QuerySession.h"12#include "clang/AST/ASTDumper.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/Frontend/ASTUnit.h"15#include "clang/Frontend/TextDiagnostic.h"16#include "llvm/Support/raw_ostream.h"17#include <optional>18 19using namespace clang::ast_matchers;20using namespace clang::ast_matchers::dynamic;21 22namespace clang {23namespace query {24 25Query::~Query() {}26 27bool InvalidQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {28 OS << ErrStr << "\n";29 return false;30}31 32bool NoOpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {33 return true;34}35 36bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {37 OS << "Available commands:\n\n"38 " match MATCHER, m MATCHER "39 "Match the loaded ASTs against the given matcher.\n"40 " let NAME MATCHER, l NAME MATCHER "41 "Give a matcher expression a name, to be used later\n"42 " "43 "as part of other expressions.\n"44 " set bind-root (true|false) "45 "Set whether to bind the root matcher to \"root\".\n"46 " set print-matcher (true|false) "47 "Set whether to print the current matcher.\n"48 " set enable-profile (true|false) "49 "Set whether to enable matcher profiling.\n"50 " set traversal <kind> "51 "Set traversal kind of clang-query session. Available kinds are:\n"52 " AsIs "53 "Print and match the AST as clang sees it. This mode is the "54 "default.\n"55 " IgnoreUnlessSpelledInSource "56 "Omit AST nodes unless spelled in the source.\n"57 " set output <feature> "58 "Set whether to output only <feature> content.\n"59 " enable output <feature> "60 "Enable <feature> content non-exclusively.\n"61 " disable output <feature> "62 "Disable <feature> content non-exclusively.\n"63 " quit, q "64 "Terminates the query session.\n\n"65 "Several commands accept a <feature> parameter. The available features "66 "are:\n\n"67 " print "68 "Pretty-print bound nodes.\n"69 " diag "70 "Diagnostic location for bound nodes.\n"71 " detailed-ast "72 "Detailed AST output for bound nodes.\n"73 " dump "74 "Detailed AST output for bound nodes (alias of detailed-ast).\n\n";75 return true;76}77 78bool QuitQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {79 QS.Terminate = true;80 return true;81}82 83namespace {84 85struct CollectBoundNodes : MatchFinder::MatchCallback {86 std::vector<BoundNodes> &Bindings;87 StringRef Unit;88 CollectBoundNodes(std::vector<BoundNodes> &Bindings, StringRef Unit)89 : Bindings(Bindings), Unit(Unit) {}90 void run(const MatchFinder::MatchResult &Result) override {91 Bindings.push_back(Result.Nodes);92 }93 StringRef getID() const override { return Unit; }94};95 96struct QueryProfiler {97 llvm::StringMap<llvm::TimeRecord> Records;98 99 ~QueryProfiler() {100 llvm::TimerGroup TG("clang-query", "clang-query matcher profiling",101 Records);102 TG.print(llvm::errs());103 llvm::errs().flush();104 }105};106 107} // namespace108 109bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {110 unsigned MatchCount = 0;111 112 std::optional<QueryProfiler> Profiler;113 if (QS.EnableProfile)114 Profiler.emplace();115 116 for (auto &AST : QS.ASTs) {117 ast_matchers::MatchFinder::MatchFinderOptions FinderOptions;118 std::optional<llvm::StringMap<llvm::TimeRecord>> Records;119 if (QS.EnableProfile) {120 Records.emplace();121 FinderOptions.CheckProfiling.emplace(*Records);122 }123 124 MatchFinder Finder(FinderOptions);125 std::vector<BoundNodes> Matches;126 DynTypedMatcher MaybeBoundMatcher = Matcher;127 if (QS.BindRoot) {128 std::optional<DynTypedMatcher> M = Matcher.tryBind("root");129 if (M)130 MaybeBoundMatcher = *M;131 }132 StringRef OrigSrcName = AST->getOriginalSourceFileName();133 CollectBoundNodes Collect(Matches, OrigSrcName);134 if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) {135 OS << "Not a valid top-level matcher.\n";136 return false;137 }138 139 ASTContext &Ctx = AST->getASTContext();140 Ctx.getParentMapContext().setTraversalKind(QS.TK);141 Finder.matchAST(Ctx);142 if (QS.EnableProfile)143 Profiler->Records[OrigSrcName] += (*Records)[OrigSrcName];144 145 if (QS.PrintMatcher) {146 SmallVector<StringRef, 4> Lines;147 Source.split(Lines, "\n");148 auto FirstLine = Lines[0];149 Lines.erase(Lines.begin(), Lines.begin() + 1);150 while (!Lines.empty() && Lines.back().empty()) {151 Lines.resize(Lines.size() - 1);152 }153 unsigned MaxLength = FirstLine.size();154 std::string PrefixText = "Matcher: ";155 OS << "\n " << PrefixText << FirstLine;156 157 for (auto Line : Lines) {158 OS << "\n" << std::string(PrefixText.size() + 2, ' ') << Line;159 MaxLength = std::max<int>(MaxLength, Line.rtrim().size());160 }161 162 OS << "\n"163 << " " << std::string(PrefixText.size() + MaxLength, '=') << "\n\n";164 }165 166 for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) {167 OS << "\nMatch #" << ++MatchCount << ":\n\n";168 169 for (auto BI = MI->getMap().begin(), BE = MI->getMap().end(); BI != BE;170 ++BI) {171 if (QS.DiagOutput) {172 clang::SourceRange R = BI->second.getSourceRange();173 if (R.isValid()) {174 TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(),175 AST->getDiagnostics().getDiagnosticOptions());176 TD.emitDiagnostic(177 FullSourceLoc(R.getBegin(), AST->getSourceManager()),178 DiagnosticsEngine::Note, "\"" + BI->first + "\" binds here",179 CharSourceRange::getTokenRange(R), {});180 }181 }182 if (QS.PrintOutput) {183 OS << "Binding for \"" << BI->first << "\":\n";184 BI->second.print(OS, AST->getASTContext().getPrintingPolicy());185 OS << "\n";186 }187 if (QS.DetailedASTOutput) {188 OS << "Binding for \"" << BI->first << "\":\n";189 ASTDumper Dumper(OS, Ctx, AST->getDiagnostics().getShowColors());190 Dumper.SetTraversalKind(QS.TK);191 Dumper.Visit(BI->second);192 OS << "\n";193 }194 }195 196 if (MI->getMap().empty())197 OS << "No bindings.\n";198 }199 }200 201 OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n");202 return true;203}204 205bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {206 if (Value) {207 QS.NamedValues[Name] = Value;208 } else {209 QS.NamedValues.erase(Name);210 }211 return true;212}213 214#ifndef _MSC_VER215const QueryKind SetQueryKind<bool>::value;216const QueryKind SetQueryKind<OutputKind>::value;217#endif218 219bool FileQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {220 auto Buffer = llvm::MemoryBuffer::getFile(StringRef{File}.trim());221 if (!Buffer) {222 if (Prefix.has_value())223 llvm::errs() << *Prefix << ": ";224 llvm::errs() << "cannot open " << File << ": "225 << Buffer.getError().message() << "\n";226 return false;227 }228 229 StringRef FileContentRef(Buffer.get()->getBuffer());230 231 while (!FileContentRef.empty()) {232 QueryRef Q = QueryParser::parse(FileContentRef, QS);233 if (!Q->run(llvm::outs(), QS))234 return false;235 FileContentRef = Q->RemainingContent;236 }237 return true;238}239 240} // namespace query241} // namespace clang242