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 private:
183   /// Get availability information of the declaration \p D.
184   AvailabilityInfo getAvailability(const Decl *D) const {
185     StringRef PlatformName = Context.getTargetInfo().getPlatformName();
186 
187     AvailabilityInfo Availability;
188     // Collect availability attributes from all redeclarations.
189     for (const auto *RD : D->redecls()) {
190       for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
191         if (A->getPlatform()->getName() != PlatformName)
192           continue;
193         Availability = AvailabilityInfo(A->getIntroduced(), A->getDeprecated(),
194                                         A->getObsoleted(), A->getUnavailable(),
195                                         /* UnconditionallyDeprecated */ false,
196                                         /* UnconditionallyUnavailable */ false);
197         break;
198       }
199 
200       if (const auto *A = RD->getAttr<UnavailableAttr>())
201         if (!A->isImplicit()) {
202           Availability.Unavailable = true;
203           Availability.UnconditionallyUnavailable = true;
204         }
205 
206       if (const auto *A = RD->getAttr<DeprecatedAttr>())
207         if (!A->isImplicit())
208           Availability.UnconditionallyDeprecated = true;
209     }
210 
211     return Availability;
212   }
213 
214   /// Collect API information for the enum constants and associate with the
215   /// parent enum.
216   void recordEnumConstants(EnumRecord *EnumRecord,
217                            const EnumDecl::enumerator_range Constants) {
218     for (const auto *Constant : Constants) {
219       // Collect symbol information.
220       StringRef Name = Constant->getName();
221       StringRef USR = API.recordUSR(Constant);
222       PresumedLoc Loc =
223           Context.getSourceManager().getPresumedLoc(Constant->getLocation());
224       AvailabilityInfo Availability = getAvailability(Constant);
225       DocComment Comment;
226       if (auto *RawComment = Context.getRawCommentForDeclNoCache(Constant))
227         Comment = RawComment->getFormattedLines(Context.getSourceManager(),
228                                                 Context.getDiagnostics());
229 
230       // Build declaration fragments and sub-heading for the enum constant.
231       DeclarationFragments Declaration =
232           DeclarationFragmentsBuilder::getFragmentsForEnumConstant(Constant);
233       DeclarationFragments SubHeading =
234           DeclarationFragmentsBuilder::getSubHeading(Constant);
235 
236       API.addEnumConstant(EnumRecord, Name, USR, Loc, Availability, Comment,
237                           Declaration, SubHeading);
238     }
239   }
240 
241   ASTContext &Context;
242   APISet API;
243 };
244 
245 class ExtractAPIConsumer : public ASTConsumer {
246 public:
247   ExtractAPIConsumer(ASTContext &Context, StringRef ProductName,
248                      std::unique_ptr<raw_pwrite_stream> OS)
249       : Visitor(Context), ProductName(ProductName), OS(std::move(OS)) {}
250 
251   void HandleTranslationUnit(ASTContext &Context) override {
252     // Use ExtractAPIVisitor to traverse symbol declarations in the context.
253     Visitor.TraverseDecl(Context.getTranslationUnitDecl());
254 
255     // Setup a SymbolGraphSerializer to write out collected API information in
256     // the Symbol Graph format.
257     // FIXME: Make the kind of APISerializer configurable.
258     SymbolGraphSerializer SGSerializer(Visitor.getAPI(), ProductName);
259     SGSerializer.serialize(*OS);
260   }
261 
262 private:
263   ExtractAPIVisitor Visitor;
264   std::string ProductName;
265   std::unique_ptr<raw_pwrite_stream> OS;
266 };
267 
268 } // namespace
269 
270 std::unique_ptr<ASTConsumer>
271 ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
272   std::unique_ptr<raw_pwrite_stream> OS = CreateOutputFile(CI, InFile);
273   if (!OS)
274     return nullptr;
275   return std::make_unique<ExtractAPIConsumer>(
276       CI.getASTContext(), CI.getInvocation().getFrontendOpts().ProductName,
277       std::move(OS));
278 }
279 
280 std::unique_ptr<raw_pwrite_stream>
281 ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
282   std::unique_ptr<raw_pwrite_stream> OS =
283       CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json",
284                                  /*RemoveFileOnSignal=*/false);
285   if (!OS)
286     return nullptr;
287   return OS;
288 }
289