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 "QuerySession.h"
11#include "clang/AST/ASTDumper.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Frontend/ASTUnit.h"
14#include "clang/Frontend/TextDiagnostic.h"
15#include "clang/Tooling/NodeIntrospection.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 traversal <kind> "
49 "Set traversal kind of clang-query session. Available kinds are:\n"
50 " AsIs "
51 "Print and match the AST as clang sees it. This mode is the "
52 "default.\n"
53 " IgnoreUnlessSpelledInSource "
54 "Omit AST nodes unless spelled in the source.\n"
55 " set output <feature> "
56 "Set whether to output only <feature> content.\n"
57 " enable output <feature> "
58 "Enable <feature> content non-exclusively.\n"
59 " disable output <feature> "
60 "Disable <feature> content non-exclusively.\n"
61 " quit, q "
62 "Terminates the query session.\n\n"
63 "Several commands accept a <feature> parameter. The available features "
64 "are:\n\n"
65 " print "
66 "Pretty-print bound nodes.\n"
67 " diag "
68 "Diagnostic location for bound nodes.\n"
69 " detailed-ast "
70 "Detailed AST output for bound nodes.\n"
71 " srcloc "
72 "Source locations and ranges 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 CollectBoundNodes(std::vector<BoundNodes> &Bindings) : Bindings(Bindings) {}
88 void run(const MatchFinder::MatchResult &Result) override {
89 Bindings.push_back(x: Result.Nodes);
90 }
91};
92
93void dumpLocations(llvm::raw_ostream &OS, DynTypedNode Node, ASTContext &Ctx,
94 const DiagnosticsEngine &Diags, SourceManager const &SM) {
95 auto Locs = clang::tooling::NodeIntrospection::GetLocations(Node);
96
97 auto PrintLocations = [](llvm::raw_ostream &OS, auto Iter, auto End) {
98 auto CommonEntry = Iter->first;
99 auto Scout = Iter;
100 SmallVector<std::string> LocationStrings;
101 while (Scout->first == CommonEntry) {
102 LocationStrings.push_back(
103 tooling::LocationCallFormatterCpp::format(Call: *Iter->second));
104 if (Scout == End)
105 break;
106 ++Scout;
107 if (Scout->first == CommonEntry)
108 ++Iter;
109 }
110 llvm::sort(C&: LocationStrings);
111 for (auto &LS : LocationStrings) {
112 OS << " * \"" << LS << "\"\n";
113 }
114 return Iter;
115 };
116
117 TextDiagnostic TD(OS, Ctx.getLangOpts(), &Diags.getDiagnosticOptions());
118
119 for (auto Iter = Locs.LocationAccessors.begin();
120 Iter != Locs.LocationAccessors.end(); ++Iter) {
121 if (!Iter->first.isValid())
122 continue;
123
124 TD.emitDiagnostic(Loc: FullSourceLoc(Iter->first, SM), Level: DiagnosticsEngine::Note,
125 Message: "source locations here", Ranges: std::nullopt, FixItHints: std::nullopt);
126
127 Iter = PrintLocations(OS, Iter, Locs.LocationAccessors.end());
128 OS << '\n';
129 }
130
131 for (auto Iter = Locs.RangeAccessors.begin();
132 Iter != Locs.RangeAccessors.end(); ++Iter) {
133
134 if (!Iter->first.getBegin().isValid())
135 continue;
136
137 if (SM.getPresumedLineNumber(Loc: Iter->first.getBegin()) !=
138 SM.getPresumedLineNumber(Loc: Iter->first.getEnd()))
139 continue;
140
141 TD.emitDiagnostic(
142 Loc: FullSourceLoc(Iter->first.getBegin(), SM), Level: DiagnosticsEngine::Note,
143 Message: "source ranges here " + Iter->first.printToString(SM),
144 Ranges: CharSourceRange::getTokenRange(R: Iter->first), FixItHints: std::nullopt);
145
146 Iter = PrintLocations(OS, Iter, Locs.RangeAccessors.end());
147 }
148 for (auto Iter = Locs.RangeAccessors.begin();
149 Iter != Locs.RangeAccessors.end(); ++Iter) {
150
151 if (!Iter->first.getBegin().isValid())
152 continue;
153
154 if (SM.getPresumedLineNumber(Loc: Iter->first.getBegin()) ==
155 SM.getPresumedLineNumber(Loc: Iter->first.getEnd()))
156 continue;
157
158 TD.emitDiagnostic(
159 Loc: FullSourceLoc(Iter->first.getBegin(), SM), Level: DiagnosticsEngine::Note,
160 Message: "source range " + Iter->first.printToString(SM) + " starting here...",
161 Ranges: CharSourceRange::getTokenRange(R: Iter->first), FixItHints: std::nullopt);
162
163 auto ColNum = SM.getPresumedColumnNumber(Loc: Iter->first.getEnd());
164 auto LastLineLoc = Iter->first.getEnd().getLocWithOffset(Offset: -(ColNum - 1));
165
166 TD.emitDiagnostic(Loc: FullSourceLoc(Iter->first.getEnd(), SM),
167 Level: DiagnosticsEngine::Note, Message: "... ending here",
168 Ranges: CharSourceRange::getTokenRange(
169 R: SourceRange(LastLineLoc, Iter->first.getEnd())),
170 FixItHints: std::nullopt);
171
172 Iter = PrintLocations(OS, Iter, Locs.RangeAccessors.end());
173 }
174 OS << "\n";
175}
176
177} // namespace
178
179bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
180 unsigned MatchCount = 0;
181
182 for (auto &AST : QS.ASTs) {
183 MatchFinder Finder;
184 std::vector<BoundNodes> Matches;
185 DynTypedMatcher MaybeBoundMatcher = Matcher;
186 if (QS.BindRoot) {
187 std::optional<DynTypedMatcher> M = Matcher.tryBind(ID: "root");
188 if (M)
189 MaybeBoundMatcher = *M;
190 }
191 CollectBoundNodes Collect(Matches);
192 if (!Finder.addDynamicMatcher(NodeMatch: MaybeBoundMatcher, Action: &Collect)) {
193 OS << "Not a valid top-level matcher.\n";
194 return false;
195 }
196
197 auto &Ctx = AST->getASTContext();
198 const auto &SM = Ctx.getSourceManager();
199 Ctx.getParentMapContext().setTraversalKind(QS.TK);
200 Finder.matchAST(Context&: Ctx);
201
202 if (QS.PrintMatcher) {
203 SmallVector<StringRef, 4> Lines;
204 Source.split(A&: Lines, Separator: "\n");
205 auto FirstLine = Lines[0];
206 Lines.erase(CS: Lines.begin(), CE: Lines.begin() + 1);
207 while (!Lines.empty() && Lines.back().empty()) {
208 Lines.resize(N: Lines.size() - 1);
209 }
210 unsigned MaxLength = FirstLine.size();
211 std::string PrefixText = "Matcher: ";
212 OS << "\n " << PrefixText << FirstLine;
213
214 for (auto Line : Lines) {
215 OS << "\n" << std::string(PrefixText.size() + 2, ' ') << Line;
216 MaxLength = std::max<int>(a: MaxLength, b: Line.rtrim().size());
217 }
218
219 OS << "\n"
220 << " " << std::string(PrefixText.size() + MaxLength, '=') << "\n\n";
221 }
222
223 for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) {
224 OS << "\nMatch #" << ++MatchCount << ":\n\n";
225
226 for (auto BI = MI->getMap().begin(), BE = MI->getMap().end(); BI != BE;
227 ++BI) {
228 if (QS.DiagOutput) {
229 clang::SourceRange R = BI->second.getSourceRange();
230 if (R.isValid()) {
231 TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(),
232 &AST->getDiagnostics().getDiagnosticOptions());
233 TD.emitDiagnostic(
234 Loc: FullSourceLoc(R.getBegin(), AST->getSourceManager()),
235 Level: DiagnosticsEngine::Note, Message: "\"" + BI->first + "\" binds here",
236 Ranges: CharSourceRange::getTokenRange(R), FixItHints: std::nullopt);
237 }
238 }
239 if (QS.PrintOutput) {
240 OS << "Binding for \"" << BI->first << "\":\n";
241 BI->second.print(OS, AST->getASTContext().getPrintingPolicy());
242 OS << "\n";
243 }
244 if (QS.DetailedASTOutput) {
245 OS << "Binding for \"" << BI->first << "\":\n";
246 const ASTContext &Ctx = AST->getASTContext();
247 ASTDumper Dumper(OS, Ctx, AST->getDiagnostics().getShowColors());
248 Dumper.SetTraversalKind(QS.TK);
249 Dumper.Visit(BI->second);
250 OS << "\n";
251 }
252 if (QS.SrcLocOutput) {
253 OS << "\n \"" << BI->first << "\" Source locations\n";
254 OS << " " << std::string(19 + BI->first.size(), '-') << '\n';
255
256 dumpLocations(OS, BI->second, Ctx, AST->getDiagnostics(), SM);
257 OS << "\n";
258 }
259 }
260
261 if (MI->getMap().empty())
262 OS << "No bindings.\n";
263 }
264 }
265
266 OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n");
267 return true;
268}
269
270bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
271 if (Value) {
272 QS.NamedValues[Name] = Value;
273 } else {
274 QS.NamedValues.erase(Key: Name);
275 }
276 return true;
277}
278
279#ifndef _MSC_VER
280const QueryKind SetQueryKind<bool>::value;
281const QueryKind SetQueryKind<OutputKind>::value;
282#endif
283
284} // namespace query
285} // namespace clang
286

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