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 | |
19 | using namespace clang::ast_matchers; |
20 | using namespace clang::ast_matchers::dynamic; |
21 | |
22 | namespace clang { |
23 | namespace query { |
24 | |
25 | Query::~Query() {} |
26 | |
27 | bool InvalidQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { |
28 | OS << ErrStr << "\n" ; |
29 | return false; |
30 | } |
31 | |
32 | bool NoOpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { |
33 | return true; |
34 | } |
35 | |
36 | bool 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 | |
78 | bool QuitQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { |
79 | QS.Terminate = true; |
80 | return true; |
81 | } |
82 | |
83 | namespace { |
84 | |
85 | struct 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 | |
93 | void 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 | |
179 | bool 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 | |
270 | bool 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 |
280 | const QueryKind SetQueryKind<bool>::value; |
281 | const QueryKind SetQueryKind<OutputKind>::value; |
282 | #endif |
283 | |
284 | } // namespace query |
285 | } // namespace clang |
286 | |