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