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   ExtractAPIVisitor(ASTContext &Context, Language Lang)
45       : Context(Context), API(Context.getTargetInfo().getTriple(), Lang) {}
46 
47   const APISet &getAPI() const { return API; }
48 
49   bool VisitVarDecl(const VarDecl *Decl) {
50     // Skip function parameters.
51     if (isa<ParmVarDecl>(Decl))
52       return true;
53 
54     // Skip non-global variables in records (struct/union/class).
55     if (Decl->getDeclContext()->isRecord())
56       return true;
57 
58     // Skip local variables inside function or method.
59     if (!Decl->isDefinedOutsideFunctionOrMethod())
60       return true;
61 
62     // If this is a template but not specialization or instantiation, skip.
63     if (Decl->getASTContext().getTemplateOrSpecializationInfo(Decl) &&
64         Decl->getTemplateSpecializationKind() == TSK_Undeclared)
65       return true;
66 
67     // Collect symbol information.
68     StringRef Name = Decl->getName();
69     StringRef USR = API.recordUSR(Decl);
70     PresumedLoc Loc =
71         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
72     AvailabilityInfo Availability = getAvailability(Decl);
73     LinkageInfo Linkage = Decl->getLinkageAndVisibility();
74     DocComment Comment;
75     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
76       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
77                                               Context.getDiagnostics());
78 
79     // Build declaration fragments and sub-heading for the variable.
80     DeclarationFragments Declaration =
81         DeclarationFragmentsBuilder::getFragmentsForVar(Decl);
82     DeclarationFragments SubHeading =
83         DeclarationFragmentsBuilder::getSubHeading(Decl);
84 
85     // Add the global variable record to the API set.
86     API.addGlobalVar(Name, USR, Loc, Availability, Linkage, Comment,
87                      Declaration, SubHeading);
88     return true;
89   }
90 
91   bool VisitFunctionDecl(const FunctionDecl *Decl) {
92     if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
93       // Skip member function in class templates.
94       if (Method->getParent()->getDescribedClassTemplate() != nullptr)
95         return true;
96 
97       // Skip methods in records.
98       for (auto P : Context.getParents(*Method)) {
99         if (P.get<CXXRecordDecl>())
100           return true;
101       }
102 
103       // Skip ConstructorDecl and DestructorDecl.
104       if (isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method))
105         return true;
106     }
107 
108     // Skip templated functions.
109     switch (Decl->getTemplatedKind()) {
110     case FunctionDecl::TK_NonTemplate:
111       break;
112     case FunctionDecl::TK_MemberSpecialization:
113     case FunctionDecl::TK_FunctionTemplateSpecialization:
114       if (auto *TemplateInfo = Decl->getTemplateSpecializationInfo()) {
115         if (!TemplateInfo->isExplicitInstantiationOrSpecialization())
116           return true;
117       }
118       break;
119     case FunctionDecl::TK_FunctionTemplate:
120     case FunctionDecl::TK_DependentFunctionTemplateSpecialization:
121       return true;
122     }
123 
124     // Collect symbol information.
125     StringRef Name = Decl->getName();
126     StringRef USR = API.recordUSR(Decl);
127     PresumedLoc Loc =
128         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
129     AvailabilityInfo Availability = getAvailability(Decl);
130     LinkageInfo Linkage = Decl->getLinkageAndVisibility();
131     DocComment Comment;
132     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
133       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
134                                               Context.getDiagnostics());
135 
136     // Build declaration fragments, sub-heading, and signature of the function.
137     DeclarationFragments Declaration =
138         DeclarationFragmentsBuilder::getFragmentsForFunction(Decl);
139     DeclarationFragments SubHeading =
140         DeclarationFragmentsBuilder::getSubHeading(Decl);
141     FunctionSignature Signature =
142         DeclarationFragmentsBuilder::getFunctionSignature(Decl);
143 
144     // Add the function record to the API set.
145     API.addFunction(Name, USR, Loc, Availability, Linkage, Comment, Declaration,
146                     SubHeading, Signature);
147     return true;
148   }
149 
150   bool VisitEnumDecl(const EnumDecl *Decl) {
151     if (!Decl->isComplete())
152       return true;
153 
154     // Skip forward declaration.
155     if (!Decl->isThisDeclarationADefinition())
156       return true;
157 
158     // Collect symbol information.
159     StringRef Name = Decl->getName();
160     StringRef USR = API.recordUSR(Decl);
161     PresumedLoc Loc =
162         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
163     AvailabilityInfo Availability = getAvailability(Decl);
164     DocComment Comment;
165     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
166       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
167                                               Context.getDiagnostics());
168 
169     // Build declaration fragments and sub-heading for the enum.
170     DeclarationFragments Declaration =
171         DeclarationFragmentsBuilder::getFragmentsForEnum(Decl);
172     DeclarationFragments SubHeading =
173         DeclarationFragmentsBuilder::getSubHeading(Decl);
174 
175     EnumRecord *EnumRecord = API.addEnum(Name, USR, Loc, Availability, Comment,
176                                          Declaration, SubHeading);
177 
178     // Now collect information about the enumerators in this enum.
179     recordEnumConstants(EnumRecord, Decl->enumerators());
180 
181     return true;
182   }
183 
184   bool VisitRecordDecl(const RecordDecl *Decl) {
185     if (!Decl->isCompleteDefinition())
186       return true;
187 
188     // Skip C++ structs/classes/unions
189     // TODO: support C++ records
190     if (isa<CXXRecordDecl>(Decl))
191       return true;
192 
193     // Collect symbol information.
194     StringRef Name = Decl->getName();
195     StringRef USR = API.recordUSR(Decl);
196     PresumedLoc Loc =
197         Context.getSourceManager().getPresumedLoc(Decl->getLocation());
198     AvailabilityInfo Availability = getAvailability(Decl);
199     DocComment Comment;
200     if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
201       Comment = RawComment->getFormattedLines(Context.getSourceManager(),
202                                               Context.getDiagnostics());
203 
204     // Build declaration fragments and sub-heading for the struct.
205     DeclarationFragments Declaration =
206         DeclarationFragmentsBuilder::getFragmentsForStruct(Decl);
207     DeclarationFragments SubHeading =
208         DeclarationFragmentsBuilder::getSubHeading(Decl);
209 
210     StructRecord *StructRecord = API.addStruct(
211         Name, USR, Loc, Availability, Comment, Declaration, SubHeading);
212 
213     // Now collect information about the fields in this struct.
214     recordStructFields(StructRecord, Decl->fields());
215 
216     return true;
217   }
218 
219 private:
220   /// Get availability information of the declaration \p D.
221   AvailabilityInfo getAvailability(const Decl *D) const {
222     StringRef PlatformName = Context.getTargetInfo().getPlatformName();
223 
224     AvailabilityInfo Availability;
225     // Collect availability attributes from all redeclarations.
226     for (const auto *RD : D->redecls()) {
227       for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
228         if (A->getPlatform()->getName() != PlatformName)
229           continue;
230         Availability = AvailabilityInfo(A->getIntroduced(), A->getDeprecated(),
231                                         A->getObsoleted(), A->getUnavailable(),
232                                         /* UnconditionallyDeprecated */ false,
233                                         /* UnconditionallyUnavailable */ false);
234         break;
235       }
236 
237       if (const auto *A = RD->getAttr<UnavailableAttr>())
238         if (!A->isImplicit()) {
239           Availability.Unavailable = true;
240           Availability.UnconditionallyUnavailable = true;
241         }
242 
243       if (const auto *A = RD->getAttr<DeprecatedAttr>())
244         if (!A->isImplicit())
245           Availability.UnconditionallyDeprecated = true;
246     }
247 
248     return Availability;
249   }
250 
251   /// Collect API information for the enum constants and associate with the
252   /// parent enum.
253   void recordEnumConstants(EnumRecord *EnumRecord,
254                            const EnumDecl::enumerator_range Constants) {
255     for (const auto *Constant : Constants) {
256       // Collect symbol information.
257       StringRef Name = Constant->getName();
258       StringRef USR = API.recordUSR(Constant);
259       PresumedLoc Loc =
260           Context.getSourceManager().getPresumedLoc(Constant->getLocation());
261       AvailabilityInfo Availability = getAvailability(Constant);
262       DocComment Comment;
263       if (auto *RawComment = Context.getRawCommentForDeclNoCache(Constant))
264         Comment = RawComment->getFormattedLines(Context.getSourceManager(),
265                                                 Context.getDiagnostics());
266 
267       // Build declaration fragments and sub-heading for the enum constant.
268       DeclarationFragments Declaration =
269           DeclarationFragmentsBuilder::getFragmentsForEnumConstant(Constant);
270       DeclarationFragments SubHeading =
271           DeclarationFragmentsBuilder::getSubHeading(Constant);
272 
273       API.addEnumConstant(EnumRecord, Name, USR, Loc, Availability, Comment,
274                           Declaration, SubHeading);
275     }
276   }
277 
278   /// Collect API information for the struct fields and associate with the
279   /// parent struct.
280   void recordStructFields(StructRecord *StructRecord,
281                           const RecordDecl::field_range Fields) {
282     for (const auto *Field : Fields) {
283       // Collect symbol information.
284       StringRef Name = Field->getName();
285       StringRef USR = API.recordUSR(Field);
286       PresumedLoc Loc =
287           Context.getSourceManager().getPresumedLoc(Field->getLocation());
288       AvailabilityInfo Availability = getAvailability(Field);
289       DocComment Comment;
290       if (auto *RawComment = Context.getRawCommentForDeclNoCache(Field))
291         Comment = RawComment->getFormattedLines(Context.getSourceManager(),
292                                                 Context.getDiagnostics());
293 
294       // Build declaration fragments and sub-heading for the struct field.
295       DeclarationFragments Declaration =
296           DeclarationFragmentsBuilder::getFragmentsForField(Field);
297       DeclarationFragments SubHeading =
298           DeclarationFragmentsBuilder::getSubHeading(Field);
299 
300       API.addStructField(StructRecord, Name, USR, Loc, Availability, Comment,
301                          Declaration, SubHeading);
302     }
303   }
304 
305   ASTContext &Context;
306   APISet API;
307 };
308 
309 class ExtractAPIConsumer : public ASTConsumer {
310 public:
311   ExtractAPIConsumer(ASTContext &Context, StringRef ProductName, Language Lang,
312                      std::unique_ptr<raw_pwrite_stream> OS)
313       : Visitor(Context, Lang), ProductName(ProductName), OS(std::move(OS)) {}
314 
315   void HandleTranslationUnit(ASTContext &Context) override {
316     // Use ExtractAPIVisitor to traverse symbol declarations in the context.
317     Visitor.TraverseDecl(Context.getTranslationUnitDecl());
318 
319     // Setup a SymbolGraphSerializer to write out collected API information in
320     // the Symbol Graph format.
321     // FIXME: Make the kind of APISerializer configurable.
322     SymbolGraphSerializer SGSerializer(Visitor.getAPI(), ProductName);
323     SGSerializer.serialize(*OS);
324   }
325 
326 private:
327   ExtractAPIVisitor Visitor;
328   std::string ProductName;
329   std::unique_ptr<raw_pwrite_stream> OS;
330 };
331 
332 } // namespace
333 
334 std::unique_ptr<ASTConsumer>
335 ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
336   std::unique_ptr<raw_pwrite_stream> OS = CreateOutputFile(CI, InFile);
337   if (!OS)
338     return nullptr;
339   return std::make_unique<ExtractAPIConsumer>(
340       CI.getASTContext(), CI.getInvocation().getFrontendOpts().ProductName,
341       CI.getFrontendOpts().Inputs.back().getKind().getLanguage(),
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