1 //===- ExtractAPI/ExtractAPIConsumer.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 /// \file
10 /// This file implements the ExtractAPIAction, and ASTVisitor/Consumer to
11 /// collect API information.
12 ///
13 //===----------------------------------------------------------------------===//
14 
15 #include "clang/AST/ASTConsumer.h"
16 #include "clang/AST/ASTContext.h"
17 #include "clang/AST/Decl.h"
18 #include "clang/AST/DeclCXX.h"
19 #include "clang/AST/ParentMapContext.h"
20 #include "clang/AST/RawCommentList.h"
21 #include "clang/AST/RecursiveASTVisitor.h"
22 #include "clang/Basic/TargetInfo.h"
23 #include "clang/ExtractAPI/API.h"
24 #include "clang/ExtractAPI/AvailabilityInfo.h"
25 #include "clang/ExtractAPI/DeclarationFragments.h"
26 #include "clang/ExtractAPI/FrontendActions.h"
27 #include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h"
28 #include "clang/Frontend/ASTConsumers.h"
29 #include "clang/Frontend/CompilerInstance.h"
30 #include "clang/Frontend/FrontendOptions.h"
31 #include "llvm/ADT/SmallVector.h"
32 #include "llvm/Support/MemoryBuffer.h"
33 #include "llvm/Support/raw_ostream.h"
34 
35 using namespace clang;
36 using namespace extractapi;
37 
38 namespace {
39 
40 /// The RecursiveASTVisitor to traverse symbol declarations and collect API
41 /// information.
42 class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> {
43 public:
44   explicit ExtractAPIVisitor(ASTContext &Context)
45       : Context(Context),
46         API(Context.getTargetInfo().getTriple(), Context.getLangOpts()) {}
47 
48   const APISet &getAPI() const { return API; }
49 
50   bool VisitVarDecl(const VarDecl *Decl) {
51     // Skip function parameters.
52     if (isa<ParmVarDecl>(Decl))
53       return true;
54 
55     // Skip non-global variables in records (struct/union/class).
56     if (Decl->getDeclContext()->isRecord())
57       return true;
58 
59     // Skip local variables inside function or method.
60     if (!Decl->isDefinedOutsideFunctionOrMethod())
61       return true;
62 
63     // If this is a template but not specialization or instantiation, skip.
64     if (Decl->getASTContext().getTemplateOrSpecializationInfo(Decl) &&
65         Decl->getTemplateSpecializationKind() == TSK_Undeclared)
66       return true;
67 
68     // Collect symbol information.
69     StringRef Name = Decl->getName();
70     StringRef USR = API.recordUSR(Decl);
71     PresumedLoc Loc =
72         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
73     AvailabilityInfo Availability = getAvailability(Decl);
74     LinkageInfo Linkage = Decl->getLinkageAndVisibility();
75     DocComment Comment;
76     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
77       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
78                                               Context.getDiagnostics());
79 
80     // Build declaration fragments and sub-heading for the variable.
81     DeclarationFragments Declaration =
82         DeclarationFragmentsBuilder::getFragmentsForVar(Decl);
83     DeclarationFragments SubHeading =
84         DeclarationFragmentsBuilder::getSubHeading(Decl);
85 
86     // Add the global variable record to the API set.
87     API.addGlobalVar(Name, USR, Loc, Availability, Linkage, Comment,
88                      Declaration, SubHeading);
89     return true;
90   }
91 
92   bool VisitFunctionDecl(const FunctionDecl *Decl) {
93     if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
94       // Skip member function in class templates.
95       if (Method->getParent()->getDescribedClassTemplate() != nullptr)
96         return true;
97 
98       // Skip methods in records.
99       for (auto P : Context.getParents(*Method)) {
100         if (P.get<CXXRecordDecl>())
101           return true;
102       }
103 
104       // Skip ConstructorDecl and DestructorDecl.
105       if (isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method))
106         return true;
107     }
108 
109     // Skip templated functions.
110     switch (Decl->getTemplatedKind()) {
111     case FunctionDecl::TK_NonTemplate:
112       break;
113     case FunctionDecl::TK_MemberSpecialization:
114     case FunctionDecl::TK_FunctionTemplateSpecialization:
115       if (auto *TemplateInfo = Decl->getTemplateSpecializationInfo()) {
116         if (!TemplateInfo->isExplicitInstantiationOrSpecialization())
117           return true;
118       }
119       break;
120     case FunctionDecl::TK_FunctionTemplate:
121     case FunctionDecl::TK_DependentFunctionTemplateSpecialization:
122       return true;
123     }
124 
125     // Collect symbol information.
126     StringRef Name = Decl->getName();
127     StringRef USR = API.recordUSR(Decl);
128     PresumedLoc Loc =
129         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
130     AvailabilityInfo Availability = getAvailability(Decl);
131     LinkageInfo Linkage = Decl->getLinkageAndVisibility();
132     DocComment Comment;
133     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
134       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
135                                               Context.getDiagnostics());
136 
137     // Build declaration fragments, sub-heading, and signature of the function.
138     DeclarationFragments Declaration =
139         DeclarationFragmentsBuilder::getFragmentsForFunction(Decl);
140     DeclarationFragments SubHeading =
141         DeclarationFragmentsBuilder::getSubHeading(Decl);
142     FunctionSignature Signature =
143         DeclarationFragmentsBuilder::getFunctionSignature(Decl);
144 
145     // Add the function record to the API set.
146     API.addFunction(Name, USR, Loc, Availability, Linkage, Comment, Declaration,
147                     SubHeading, Signature);
148     return true;
149   }
150 
151   bool VisitEnumDecl(const EnumDecl *Decl) {
152     if (!Decl->isComplete())
153       return true;
154 
155     // Skip forward declaration.
156     if (!Decl->isThisDeclarationADefinition())
157       return true;
158 
159     // Collect symbol information.
160     StringRef Name = Decl->getName();
161     StringRef USR = API.recordUSR(Decl);
162     PresumedLoc Loc =
163         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
164     AvailabilityInfo Availability = getAvailability(Decl);
165     DocComment Comment;
166     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
167       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
168                                               Context.getDiagnostics());
169 
170     // Build declaration fragments and sub-heading for the enum.
171     DeclarationFragments Declaration =
172         DeclarationFragmentsBuilder::getFragmentsForEnum(Decl);
173     DeclarationFragments SubHeading =
174         DeclarationFragmentsBuilder::getSubHeading(Decl);
175 
176     EnumRecord *EnumRecord = API.addEnum(Name, USR, Loc, Availability, Comment,
177                                          Declaration, SubHeading);
178 
179     // Now collect information about the enumerators in this enum.
180     recordEnumConstants(EnumRecord, Decl->enumerators());
181 
182     return true;
183   }
184 
185   bool VisitRecordDecl(const RecordDecl *Decl) {
186     if (!Decl->isCompleteDefinition())
187       return true;
188 
189     // Skip C++ structs/classes/unions
190     // TODO: support C++ records
191     if (isa<CXXRecordDecl>(Decl))
192       return true;
193 
194     // Collect symbol information.
195     StringRef Name = Decl->getName();
196     StringRef USR = API.recordUSR(Decl);
197     PresumedLoc Loc =
198         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
199     AvailabilityInfo Availability = getAvailability(Decl);
200     DocComment Comment;
201     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
202       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
203                                               Context.getDiagnostics());
204 
205     // Build declaration fragments and sub-heading for the struct.
206     DeclarationFragments Declaration =
207         DeclarationFragmentsBuilder::getFragmentsForStruct(Decl);
208     DeclarationFragments SubHeading =
209         DeclarationFragmentsBuilder::getSubHeading(Decl);
210 
211     StructRecord *StructRecord = API.addStruct(
212         Name, USR, Loc, Availability, Comment, Declaration, SubHeading);
213 
214     // Now collect information about the fields in this struct.
215     recordStructFields(StructRecord, Decl->fields());
216 
217     return true;
218   }
219 
220 private:
221   /// Get availability information of the declaration \p D.
222   AvailabilityInfo getAvailability(const Decl *D) const {
223     StringRef PlatformName = Context.getTargetInfo().getPlatformName();
224 
225     AvailabilityInfo Availability;
226     // Collect availability attributes from all redeclarations.
227     for (const auto *RD : D->redecls()) {
228       for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
229         if (A->getPlatform()->getName() != PlatformName)
230           continue;
231         Availability = AvailabilityInfo(A->getIntroduced(), A->getDeprecated(),
232                                         A->getObsoleted(), A->getUnavailable(),
233                                         /* UnconditionallyDeprecated */ false,
234                                         /* UnconditionallyUnavailable */ false);
235         break;
236       }
237 
238       if (const auto *A = RD->getAttr<UnavailableAttr>())
239         if (!A->isImplicit()) {
240           Availability.Unavailable = true;
241           Availability.UnconditionallyUnavailable = true;
242         }
243 
244       if (const auto *A = RD->getAttr<DeprecatedAttr>())
245         if (!A->isImplicit())
246           Availability.UnconditionallyDeprecated = true;
247     }
248 
249     return Availability;
250   }
251 
252   /// Collect API information for the enum constants and associate with the
253   /// parent enum.
254   void recordEnumConstants(EnumRecord *EnumRecord,
255                            const EnumDecl::enumerator_range Constants) {
256     for (const auto *Constant : Constants) {
257       // Collect symbol information.
258       StringRef Name = Constant->getName();
259       StringRef USR = API.recordUSR(Constant);
260       PresumedLoc Loc =
261           Context.getSourceManager().getPresumedLoc(Constant->getLocation());
262       AvailabilityInfo Availability = getAvailability(Constant);
263       DocComment Comment;
264       if (auto *RawComment = Context.getRawCommentForDeclNoCache(Constant))
265         Comment = RawComment->getFormattedLines(Context.getSourceManager(),
266                                                 Context.getDiagnostics());
267 
268       // Build declaration fragments and sub-heading for the enum constant.
269       DeclarationFragments Declaration =
270           DeclarationFragmentsBuilder::getFragmentsForEnumConstant(Constant);
271       DeclarationFragments SubHeading =
272           DeclarationFragmentsBuilder::getSubHeading(Constant);
273 
274       API.addEnumConstant(EnumRecord, Name, USR, Loc, Availability, Comment,
275                           Declaration, SubHeading);
276     }
277   }
278 
279   /// Collect API information for the struct fields and associate with the
280   /// parent struct.
281   void recordStructFields(StructRecord *StructRecord,
282                           const RecordDecl::field_range Fields) {
283     for (const auto *Field : Fields) {
284       // Collect symbol information.
285       StringRef Name = Field->getName();
286       StringRef USR = API.recordUSR(Field);
287       PresumedLoc Loc =
288           Context.getSourceManager().getPresumedLoc(Field->getLocation());
289       AvailabilityInfo Availability = getAvailability(Field);
290       DocComment Comment;
291       if (auto *RawComment = Context.getRawCommentForDeclNoCache(Field))
292         Comment = RawComment->getFormattedLines(Context.getSourceManager(),
293                                                 Context.getDiagnostics());
294 
295       // Build declaration fragments and sub-heading for the struct field.
296       DeclarationFragments Declaration =
297           DeclarationFragmentsBuilder::getFragmentsForField(Field);
298       DeclarationFragments SubHeading =
299           DeclarationFragmentsBuilder::getSubHeading(Field);
300 
301       API.addStructField(StructRecord, Name, USR, Loc, Availability, Comment,
302                          Declaration, SubHeading);
303     }
304   }
305 
306   ASTContext &Context;
307   APISet API;
308 };
309 
310 class ExtractAPIConsumer : public ASTConsumer {
311 public:
312   ExtractAPIConsumer(ASTContext &Context, StringRef ProductName,
313                      std::unique_ptr<raw_pwrite_stream> OS)
314       : Visitor(Context), ProductName(ProductName), OS(std::move(OS)) {}
315 
316   void HandleTranslationUnit(ASTContext &Context) override {
317     // Use ExtractAPIVisitor to traverse symbol declarations in the context.
318     Visitor.TraverseDecl(Context.getTranslationUnitDecl());
319 
320     // Setup a SymbolGraphSerializer to write out collected API information in
321     // the Symbol Graph format.
322     // FIXME: Make the kind of APISerializer configurable.
323     SymbolGraphSerializer SGSerializer(Visitor.getAPI(), ProductName);
324     SGSerializer.serialize(*OS);
325   }
326 
327 private:
328   ExtractAPIVisitor Visitor;
329   std::string ProductName;
330   std::unique_ptr<raw_pwrite_stream> OS;
331 };
332 
333 } // namespace
334 
335 std::unique_ptr<ASTConsumer>
336 ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
337   std::unique_ptr<raw_pwrite_stream> OS = CreateOutputFile(CI, InFile);
338   if (!OS)
339     return nullptr;
340   return std::make_unique<ExtractAPIConsumer>(
341       CI.getASTContext(), CI.getInvocation().getFrontendOpts().ProductName,
342       std::move(OS));
343 }
344 
345 bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) {
346   auto &Inputs = CI.getFrontendOpts().Inputs;
347   if (Inputs.empty())
348     return true;
349 
350   auto Kind = Inputs[0].getKind();
351 
352   // Convert the header file inputs into a single input buffer.
353   SmallString<256> HeaderContents;
354   for (const FrontendInputFile &FIF : Inputs) {
355     if (Kind.isObjectiveC())
356       HeaderContents += "#import";
357     else
358       HeaderContents += "#include";
359     HeaderContents += " \"";
360     HeaderContents += FIF.getFile();
361     HeaderContents += "\"\n";
362   }
363 
364   Buffer = llvm::MemoryBuffer::getMemBufferCopy(HeaderContents,
365                                                 getInputBufferName());
366 
367   // Set that buffer up as our "real" input in the CompilerInstance.
368   Inputs.clear();
369   Inputs.emplace_back(Buffer->getMemBufferRef(), Kind, /*IsSystem*/ false);
370 
371   return true;
372 }
373 
374 std::unique_ptr<raw_pwrite_stream>
375 ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
376   std::unique_ptr<raw_pwrite_stream> OS =
377       CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json",
378                                  /*RemoveFileOnSignal=*/false);
379   if (!OS)
380     return nullptr;
381   return OS;
382 }
383