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-exception
6//
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(x: 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(OS&: llvm::errs());
103 llvm::errs().flush();
104 }
105};
106
107} // namespace
108
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(args&: *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(ID: "root");
129 if (M)
130 MaybeBoundMatcher = *M;
131 }
132 StringRef OrigSrcName = AST->getOriginalSourceFileName();
133 CollectBoundNodes Collect(Matches, OrigSrcName);
134 if (!Finder.addDynamicMatcher(NodeMatch: MaybeBoundMatcher, Action: &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(Context&: Ctx);
142 if (QS.EnableProfile)
143 Profiler->Records[OrigSrcName] += (*Records)[OrigSrcName];
144
145 if (QS.PrintMatcher) {
146 SmallVector<StringRef, 4> Lines;
147 Source.split(A&: Lines, Separator: "\n");
148 auto FirstLine = Lines[0];
149 Lines.erase(CS: Lines.begin(), CE: Lines.begin() + 1);
150 while (!Lines.empty() && Lines.back().empty()) {
151 Lines.resize(N: 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>(a: MaxLength, b: 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 Loc: FullSourceLoc(R.getBegin(), AST->getSourceManager()),
178 Level: DiagnosticsEngine::Note, Message: "\"" + BI->first + "\" binds here",
179 Ranges: CharSourceRange::getTokenRange(R), FixItHints: {});
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(Key: Name);
210 }
211 return true;
212}
213
214#ifndef _MSC_VER
215const QueryKind SetQueryKind<bool>::value;
216const QueryKind SetQueryKind<OutputKind>::value;
217#endif
218
219bool FileQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
220 auto Buffer = llvm::MemoryBuffer::getFile(Filename: 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(Line: FileContentRef, QS);
233 if (!Q->run(OS&: llvm::outs(), QS))
234 return false;
235 FileContentRef = Q->RemainingContent;
236 }
237 return true;
238}
239
240} // namespace query
241} // namespace clang
242

source code of clang-tools-extra/clang-query/Query.cpp