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 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 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 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 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 96 struct 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 } // namespace 108 109 bool 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 205 bool 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_VER 215 const QueryKind SetQueryKind<bool>::value; 216 const QueryKind SetQueryKind<OutputKind>::value; 217 #endif 218 219 bool 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 query 241 } // namespace clang 242