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