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