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 
18 using namespace clang::ast_matchers;
19 using namespace clang::ast_matchers::dynamic;
20 
21 namespace clang {
22 namespace query {
23 
~Query()24 Query::~Query() {}
25 
run(llvm::raw_ostream & OS,QuerySession & QS) const26 bool InvalidQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
27   OS << ErrStr << "\n";
28   return false;
29 }
30 
run(llvm::raw_ostream & OS,QuerySession & QS) const31 bool NoOpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
32   return true;
33 }
34 
run(llvm::raw_ostream & OS,QuerySession & QS) const35 bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
36   OS << "Available commands:\n\n"
37         "  match MATCHER, m MATCHER          "
38         "Match the loaded ASTs against the given matcher.\n"
39         "  let NAME MATCHER, l NAME MATCHER  "
40         "Give a matcher expression a name, to be used later\n"
41         "                                    "
42         "as part of other expressions.\n"
43         "  set bind-root (true|false)        "
44         "Set whether to bind the root matcher to \"root\".\n"
45         "  set print-matcher (true|false)    "
46         "Set whether to print the current matcher,\n"
47         "  set traversal <kind>              "
48         "Set traversal kind of clang-query session. Available kinds are:\n"
49         "    AsIs                            "
50         "Print and match the AST as clang sees it.  This mode is the "
51         "default.\n"
52         "    IgnoreUnlessSpelledInSource     "
53         "Omit AST nodes unless spelled in the source.\n"
54         "  set output <feature>              "
55         "Set whether to output only <feature> content.\n"
56         "  enable output <feature>           "
57         "Enable <feature> content non-exclusively.\n"
58         "  disable output <feature>          "
59         "Disable <feature> content non-exclusively.\n"
60         "  quit, q                           "
61         "Terminates the query session.\n\n"
62         "Several commands accept a <feature> parameter. The available features "
63         "are:\n\n"
64         "  print                             "
65         "Pretty-print bound nodes.\n"
66         "  diag                              "
67         "Diagnostic location for bound nodes.\n"
68         "  detailed-ast                      "
69         "Detailed AST output for bound nodes.\n"
70         "  srcloc                            "
71         "Source locations and ranges for bound nodes.\n"
72         "  dump                              "
73         "Detailed AST output for bound nodes (alias of detailed-ast).\n\n";
74   return true;
75 }
76 
run(llvm::raw_ostream & OS,QuerySession & QS) const77 bool QuitQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
78   QS.Terminate = true;
79   return true;
80 }
81 
82 namespace {
83 
84 struct CollectBoundNodes : MatchFinder::MatchCallback {
85   std::vector<BoundNodes> &Bindings;
CollectBoundNodesclang::query::__anonda226f0a0111::CollectBoundNodes86   CollectBoundNodes(std::vector<BoundNodes> &Bindings) : Bindings(Bindings) {}
runclang::query::__anonda226f0a0111::CollectBoundNodes87   void run(const MatchFinder::MatchResult &Result) override {
88     Bindings.push_back(Result.Nodes);
89   }
90 };
91 
dumpLocations(llvm::raw_ostream & OS,DynTypedNode Node,ASTContext & Ctx,const DiagnosticsEngine & Diags,SourceManager const & SM)92 void dumpLocations(llvm::raw_ostream &OS, DynTypedNode Node, ASTContext &Ctx,
93                    const DiagnosticsEngine &Diags, SourceManager const &SM) {
94   auto Locs = clang::tooling::NodeIntrospection::GetLocations(Node);
95 
96   auto PrintLocations = [](llvm::raw_ostream &OS, auto Iter, auto End) {
97     auto CommonEntry = Iter->first;
98     auto Scout = Iter;
99     SmallVector<std::string> LocationStrings;
100     while (Scout->first == CommonEntry) {
101       LocationStrings.push_back(
102           tooling::LocationCallFormatterCpp::format(*Iter->second));
103       if (Scout == End)
104         break;
105       ++Scout;
106       if (Scout->first == CommonEntry)
107         ++Iter;
108     }
109     llvm::sort(LocationStrings);
110     for (auto &LS : LocationStrings) {
111       OS << " * \"" << LS << "\"\n";
112     }
113     return Iter;
114   };
115 
116   TextDiagnostic TD(OS, Ctx.getLangOpts(), &Diags.getDiagnosticOptions());
117 
118   for (auto Iter = Locs.LocationAccessors.begin();
119        Iter != Locs.LocationAccessors.end(); ++Iter) {
120     if (!Iter->first.isValid())
121       continue;
122 
123     TD.emitDiagnostic(FullSourceLoc(Iter->first, SM), DiagnosticsEngine::Note,
124                       "source locations here", None, None);
125 
126     Iter = PrintLocations(OS, Iter, Locs.LocationAccessors.end());
127     OS << '\n';
128   }
129 
130   for (auto Iter = Locs.RangeAccessors.begin();
131        Iter != Locs.RangeAccessors.end(); ++Iter) {
132 
133     if (!Iter->first.getBegin().isValid())
134       continue;
135 
136     if (SM.getPresumedLineNumber(Iter->first.getBegin()) !=
137         SM.getPresumedLineNumber(Iter->first.getEnd()))
138       continue;
139 
140     TD.emitDiagnostic(FullSourceLoc(Iter->first.getBegin(), SM),
141                       DiagnosticsEngine::Note,
142                       "source ranges here " + Iter->first.printToString(SM),
143                       CharSourceRange::getTokenRange(Iter->first), None);
144 
145     Iter = PrintLocations(OS, Iter, Locs.RangeAccessors.end());
146   }
147   for (auto Iter = Locs.RangeAccessors.begin();
148        Iter != Locs.RangeAccessors.end(); ++Iter) {
149 
150     if (!Iter->first.getBegin().isValid())
151       continue;
152 
153     if (SM.getPresumedLineNumber(Iter->first.getBegin()) ==
154         SM.getPresumedLineNumber(Iter->first.getEnd()))
155       continue;
156 
157     TD.emitDiagnostic(
158         FullSourceLoc(Iter->first.getBegin(), SM), DiagnosticsEngine::Note,
159         "source range " + Iter->first.printToString(SM) + " starting here...",
160         CharSourceRange::getTokenRange(Iter->first), None);
161 
162     auto ColNum = SM.getPresumedColumnNumber(Iter->first.getEnd());
163     auto LastLineLoc = Iter->first.getEnd().getLocWithOffset(-(ColNum - 1));
164 
165     TD.emitDiagnostic(FullSourceLoc(Iter->first.getEnd(), SM),
166                       DiagnosticsEngine::Note, "... ending here",
167                       CharSourceRange::getTokenRange(
168                           SourceRange(LastLineLoc, Iter->first.getEnd())),
169                       None);
170 
171     Iter = PrintLocations(OS, Iter, Locs.RangeAccessors.end());
172   }
173   OS << "\n";
174 }
175 
176 } // namespace
177 
run(llvm::raw_ostream & OS,QuerySession & QS) const178 bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
179   unsigned MatchCount = 0;
180 
181   for (auto &AST : QS.ASTs) {
182     MatchFinder Finder;
183     std::vector<BoundNodes> Matches;
184     DynTypedMatcher MaybeBoundMatcher = Matcher;
185     if (QS.BindRoot) {
186       llvm::Optional<DynTypedMatcher> M = Matcher.tryBind("root");
187       if (M)
188         MaybeBoundMatcher = *M;
189     }
190     CollectBoundNodes Collect(Matches);
191     if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) {
192       OS << "Not a valid top-level matcher.\n";
193       return false;
194     }
195 
196     auto &Ctx = AST->getASTContext();
197     const auto &SM = Ctx.getSourceManager();
198     Ctx.getParentMapContext().setTraversalKind(QS.TK);
199     Finder.matchAST(Ctx);
200 
201     if (QS.PrintMatcher) {
202       SmallVector<StringRef, 4> Lines;
203       Source.split(Lines, "\n");
204       auto FirstLine = Lines[0];
205       Lines.erase(Lines.begin(), Lines.begin() + 1);
206       while (!Lines.empty() && Lines.back().empty()) {
207         Lines.resize(Lines.size() - 1);
208       }
209       unsigned MaxLength = FirstLine.size();
210       std::string PrefixText = "Matcher: ";
211       OS << "\n  " << PrefixText << FirstLine;
212 
213       for (auto Line : Lines) {
214         OS << "\n" << std::string(PrefixText.size() + 2, ' ') << Line;
215         MaxLength = std::max<int>(MaxLength, Line.rtrim().size());
216       }
217 
218       OS << "\n"
219          << "  " << std::string(PrefixText.size() + MaxLength, '=') << "\n\n";
220     }
221 
222     for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) {
223       OS << "\nMatch #" << ++MatchCount << ":\n\n";
224 
225       for (auto BI = MI->getMap().begin(), BE = MI->getMap().end(); BI != BE;
226            ++BI) {
227         if (QS.DiagOutput) {
228           clang::SourceRange R = BI->second.getSourceRange();
229           if (R.isValid()) {
230             TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(),
231                               &AST->getDiagnostics().getDiagnosticOptions());
232             TD.emitDiagnostic(
233                 FullSourceLoc(R.getBegin(), AST->getSourceManager()),
234                 DiagnosticsEngine::Note, "\"" + BI->first + "\" binds here",
235                 CharSourceRange::getTokenRange(R), None);
236           }
237         }
238         if (QS.PrintOutput) {
239           OS << "Binding for \"" << BI->first << "\":\n";
240           BI->second.print(OS, AST->getASTContext().getPrintingPolicy());
241           OS << "\n";
242         }
243         if (QS.DetailedASTOutput) {
244           OS << "Binding for \"" << BI->first << "\":\n";
245           const ASTContext &Ctx = AST->getASTContext();
246           ASTDumper Dumper(OS, Ctx, AST->getDiagnostics().getShowColors());
247           Dumper.SetTraversalKind(QS.TK);
248           Dumper.Visit(BI->second);
249           OS << "\n";
250         }
251         if (QS.SrcLocOutput) {
252           OS << "\n  \"" << BI->first << "\" Source locations\n";
253           OS << "  " << std::string(19 + BI->first.size(), '-') << '\n';
254 
255           dumpLocations(OS, BI->second, Ctx, AST->getDiagnostics(), SM);
256           OS << "\n";
257         }
258       }
259 
260       if (MI->getMap().empty())
261         OS << "No bindings.\n";
262     }
263   }
264 
265   OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n");
266   return true;
267 }
268 
run(llvm::raw_ostream & OS,QuerySession & QS) const269 bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
270   if (Value) {
271     QS.NamedValues[Name] = Value;
272   } else {
273     QS.NamedValues.erase(Name);
274   }
275   return true;
276 }
277 
278 #ifndef _MSC_VER
279 const QueryKind SetQueryKind<bool>::value;
280 const QueryKind SetQueryKind<OutputKind>::value;
281 #endif
282 
283 } // namespace query
284 } // namespace clang
285