1 //===- ASTSrcLocProcessor.cpp --------------------------------*- C++ -*----===//
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 "ASTSrcLocProcessor.h"
10 
11 #include "clang/Frontend/CompilerInstance.h"
12 #include "llvm/Support/JSON.h"
13 #include "llvm/Support/MemoryBuffer.h"
14 
15 using namespace clang::tooling;
16 using namespace llvm;
17 using namespace clang::ast_matchers;
18 
19 ASTSrcLocProcessor::ASTSrcLocProcessor(StringRef JsonPath)
20     : JsonPath(JsonPath) {
21 
22   MatchFinder::MatchFinderOptions FinderOptions;
23 
24   Finder = std::make_unique<MatchFinder>(std::move(FinderOptions));
25   Finder->addMatcher(
26           cxxRecordDecl(
27               isDefinition(),
28               isSameOrDerivedFrom(
29                   // TODO: Extend this with other clades
30                   namedDecl(hasAnyName("clang::Stmt", "clang::Decl",
31                                        "clang::CXXCtorInitializer",
32                                        "clang::NestedNameSpecifierLoc",
33                                        "clang::TemplateArgumentLoc",
34                                        "clang::CXXBaseSpecifier",
35                                        "clang::TypeLoc"))
36                       .bind("nodeClade")),
37               optionally(isDerivedFrom(cxxRecordDecl().bind("derivedFrom"))))
38               .bind("className"),
39       this);
40   Finder->addMatcher(
41           cxxRecordDecl(isDefinition(), hasAnyName("clang::PointerLikeTypeLoc",
42                                                    "clang::TypeofLikeTypeLoc"))
43               .bind("templateName"),
44       this);
45 }
46 
47 std::unique_ptr<clang::ASTConsumer>
48 ASTSrcLocProcessor::createASTConsumer(clang::CompilerInstance &Compiler,
49                                       StringRef File) {
50   return Finder->newASTConsumer();
51 }
52 
53 llvm::json::Object toJSON(llvm::StringMap<std::vector<StringRef>> const &Obj) {
54   using llvm::json::toJSON;
55 
56   llvm::json::Object JsonObj;
57   for (const auto &Item : Obj) {
58     JsonObj[Item.first()] = Item.second;
59   }
60   return JsonObj;
61 }
62 
63 llvm::json::Object toJSON(llvm::StringMap<std::string> const &Obj) {
64   using llvm::json::toJSON;
65 
66   llvm::json::Object JsonObj;
67   for (const auto &Item : Obj) {
68     JsonObj[Item.first()] = Item.second;
69   }
70   return JsonObj;
71 }
72 
73 llvm::json::Object toJSON(ClassData const &Obj) {
74   llvm::json::Object JsonObj;
75 
76   if (!Obj.ASTClassLocations.empty())
77     JsonObj["sourceLocations"] = Obj.ASTClassLocations;
78   if (!Obj.ASTClassRanges.empty())
79     JsonObj["sourceRanges"] = Obj.ASTClassRanges;
80   if (!Obj.TemplateParms.empty())
81     JsonObj["templateParms"] = Obj.TemplateParms;
82   if (!Obj.TypeSourceInfos.empty())
83     JsonObj["typeSourceInfos"] = Obj.TypeSourceInfos;
84   if (!Obj.TypeLocs.empty())
85     JsonObj["typeLocs"] = Obj.TypeLocs;
86   return JsonObj;
87 }
88 
89 llvm::json::Object toJSON(llvm::StringMap<ClassData> const &Obj) {
90   using llvm::json::toJSON;
91 
92   llvm::json::Object JsonObj;
93   for (const auto &Item : Obj)
94     JsonObj[Item.first()] = ::toJSON(Item.second);
95   return JsonObj;
96 }
97 
98 void WriteJSON(StringRef JsonPath, llvm::json::Object &&ClassInheritance,
99                llvm::json::Object &&ClassesInClade,
100                llvm::json::Object &&ClassEntries) {
101   llvm::json::Object JsonObj;
102 
103   using llvm::json::toJSON;
104 
105   JsonObj["classInheritance"] = std::move(ClassInheritance);
106   JsonObj["classesInClade"] = std::move(ClassesInClade);
107   JsonObj["classEntries"] = std::move(ClassEntries);
108 
109   llvm::json::Value JsonVal(std::move(JsonObj));
110 
111   bool WriteChange = false;
112   std::string OutString;
113   if (auto ExistingOrErr = MemoryBuffer::getFile(JsonPath, /*IsText=*/true)) {
114     raw_string_ostream Out(OutString);
115     Out << formatv("{0:2}", JsonVal);
116     if (ExistingOrErr.get()->getBuffer() == Out.str())
117       return;
118     WriteChange = true;
119   }
120 
121   std::error_code EC;
122   llvm::raw_fd_ostream JsonOut(JsonPath, EC, llvm::sys::fs::F_Text);
123   if (EC)
124     return;
125 
126   if (WriteChange)
127     JsonOut << OutString;
128   else
129     JsonOut << formatv("{0:2}", JsonVal);
130 }
131 
132 void ASTSrcLocProcessor::generate() {
133   WriteJSON(JsonPath, ::toJSON(ClassInheritance), ::toJSON(ClassesInClade),
134             ::toJSON(ClassEntries));
135 }
136 
137 void ASTSrcLocProcessor::generateEmpty() { WriteJSON(JsonPath, {}, {}, {}); }
138 
139 std::vector<std::string>
140 CaptureMethods(std::string TypeString, const clang::CXXRecordDecl *ASTClass,
141                const MatchFinder::MatchResult &Result) {
142 
143   auto publicAccessor = [](auto... InnerMatcher) {
144     return cxxMethodDecl(isPublic(), parameterCountIs(0), isConst(),
145                          InnerMatcher...);
146   };
147 
148   auto BoundNodesVec = match(
149       findAll(
150           publicAccessor(
151               ofClass(cxxRecordDecl(
152                   equalsNode(ASTClass),
153                   optionally(isDerivedFrom(
154                       cxxRecordDecl(hasAnyName("clang::Stmt", "clang::Decl"))
155                           .bind("stmtOrDeclBase"))),
156                   optionally(isDerivedFrom(
157                       cxxRecordDecl(hasName("clang::Expr")).bind("exprBase"))),
158                   optionally(
159                       isDerivedFrom(cxxRecordDecl(hasName("clang::TypeLoc"))
160                                         .bind("typeLocBase"))))),
161               returns(asString(TypeString)))
162               .bind("classMethod")),
163       *ASTClass, *Result.Context);
164 
165   std::vector<std::string> Methods;
166   for (const auto &BN : BoundNodesVec) {
167     if (const auto *Node = BN.getNodeAs<clang::NamedDecl>("classMethod")) {
168       const auto *StmtOrDeclBase =
169           BN.getNodeAs<clang::CXXRecordDecl>("stmtOrDeclBase");
170       const auto *TypeLocBase =
171           BN.getNodeAs<clang::CXXRecordDecl>("typeLocBase");
172       const auto *ExprBase = BN.getNodeAs<clang::CXXRecordDecl>("exprBase");
173       // The clang AST has several methods on base classes which are overriden
174       // pseudo-virtually by derived classes.
175       // We record only the pseudo-virtual methods on the base classes to
176       // avoid duplication.
177       if (StmtOrDeclBase &&
178           (Node->getName() == "getBeginLoc" || Node->getName() == "getEndLoc" ||
179            Node->getName() == "getSourceRange"))
180         continue;
181       if (ExprBase && Node->getName() == "getExprLoc")
182         continue;
183       if (TypeLocBase && Node->getName() == "getLocalSourceRange")
184         continue;
185       if ((ASTClass->getName() == "PointerLikeTypeLoc" ||
186            ASTClass->getName() == "TypeofLikeTypeLoc") &&
187           Node->getName() == "getLocalSourceRange")
188         continue;
189       Methods.push_back(Node->getName().str());
190     }
191   }
192   return Methods;
193 }
194 
195 void ASTSrcLocProcessor::run(const MatchFinder::MatchResult &Result) {
196 
197   const auto *ASTClass =
198       Result.Nodes.getNodeAs<clang::CXXRecordDecl>("className");
199 
200   StringRef CladeName;
201   if (ASTClass) {
202     if (const auto *NodeClade =
203             Result.Nodes.getNodeAs<clang::CXXRecordDecl>("nodeClade"))
204       CladeName = NodeClade->getName();
205   } else {
206     ASTClass = Result.Nodes.getNodeAs<clang::CXXRecordDecl>("templateName");
207     CladeName = "TypeLoc";
208   }
209 
210   StringRef ClassName = ASTClass->getName();
211 
212   ClassData CD;
213 
214   CD.ASTClassLocations =
215       CaptureMethods("class clang::SourceLocation", ASTClass, Result);
216   CD.ASTClassRanges =
217       CaptureMethods("class clang::SourceRange", ASTClass, Result);
218   CD.TypeSourceInfos =
219       CaptureMethods("class clang::TypeSourceInfo *", ASTClass, Result);
220   CD.TypeLocs = CaptureMethods("class clang::TypeLoc", ASTClass, Result);
221 
222   if (const auto *DerivedFrom =
223           Result.Nodes.getNodeAs<clang::CXXRecordDecl>("derivedFrom")) {
224 
225     if (const auto *Templ =
226             llvm::dyn_cast<clang::ClassTemplateSpecializationDecl>(
227                 DerivedFrom)) {
228 
229       const auto &TArgs = Templ->getTemplateArgs();
230 
231       SmallString<256> TArgsString;
232       llvm::raw_svector_ostream OS(TArgsString);
233       OS << DerivedFrom->getName() << '<';
234 
235       clang::PrintingPolicy PPol(Result.Context->getLangOpts());
236       PPol.TerseOutput = true;
237 
238       for (unsigned I = 0; I < TArgs.size(); ++I) {
239         if (I > 0)
240           OS << ", ";
241         TArgs.get(I).getAsType().print(OS, PPol);
242       }
243       OS << '>';
244 
245       ClassInheritance[ClassName] = TArgsString.str().str();
246     } else {
247       ClassInheritance[ClassName] = DerivedFrom->getName().str();
248     }
249   }
250 
251   if (const auto *Templ = ASTClass->getDescribedClassTemplate()) {
252     if (auto *TParams = Templ->getTemplateParameters()) {
253       for (const auto &TParam : *TParams) {
254         CD.TemplateParms.push_back(TParam->getName().str());
255       }
256     }
257   }
258 
259   ClassEntries[ClassName] = CD;
260   ClassesInClade[CladeName].push_back(ClassName);
261 }
262