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