1 //===- ClangExtDefMapGen.cpp ---------------------------------------------===// 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 // Clang tool which creates a list of defined functions and the files in which 10 // they are defined. 11 // 12 //===--------------------------------------------------------------------===// 13 14 #include "clang/AST/ASTConsumer.h" 15 #include "clang/AST/ASTContext.h" 16 #include "clang/Basic/DiagnosticOptions.h" 17 #include "clang/Basic/SourceManager.h" 18 #include "clang/CrossTU/CrossTranslationUnit.h" 19 #include "clang/Frontend/CompilerInstance.h" 20 #include "clang/Frontend/FrontendActions.h" 21 #include "clang/Frontend/TextDiagnosticPrinter.h" 22 #include "clang/Tooling/CommonOptionsParser.h" 23 #include "clang/Tooling/Tooling.h" 24 #include "llvm/Support/CommandLine.h" 25 #include "llvm/Support/Signals.h" 26 #include <optional> 27 #include <sstream> 28 #include <string> 29 30 using namespace llvm; 31 using namespace clang; 32 using namespace clang::cross_tu; 33 using namespace clang::tooling; 34 35 static cl::OptionCategory 36 ClangExtDefMapGenCategory("clang-extdefmapgen options"); 37 38 class MapExtDefNamesConsumer : public ASTConsumer { 39 public: 40 MapExtDefNamesConsumer(ASTContext &Context, 41 StringRef astFilePath = StringRef()) 42 : Ctx(Context), SM(Context.getSourceManager()) { 43 CurrentFileName = astFilePath.str(); 44 } 45 46 ~MapExtDefNamesConsumer() { 47 // Flush results to standard output. 48 llvm::outs() << createCrossTUIndexString(Index); 49 } 50 51 void HandleTranslationUnit(ASTContext &Context) override { 52 handleDecl(Context.getTranslationUnitDecl()); 53 } 54 55 private: 56 void handleDecl(const Decl *D); 57 void addIfInMain(const DeclaratorDecl *DD, SourceLocation defStart); 58 59 ASTContext &Ctx; 60 SourceManager &SM; 61 llvm::StringMap<std::string> Index; 62 std::string CurrentFileName; 63 }; 64 65 void MapExtDefNamesConsumer::handleDecl(const Decl *D) { 66 if (!D) 67 return; 68 69 if (const auto *FD = dyn_cast<FunctionDecl>(D)) { 70 if (FD->isThisDeclarationADefinition()) 71 if (const Stmt *Body = FD->getBody()) 72 addIfInMain(FD, Body->getBeginLoc()); 73 } else if (const auto *VD = dyn_cast<VarDecl>(D)) { 74 if (cross_tu::shouldImport(VD, Ctx) && VD->hasInit()) 75 if (const Expr *Init = VD->getInit()) 76 addIfInMain(VD, Init->getBeginLoc()); 77 } 78 79 if (const auto *DC = dyn_cast<DeclContext>(D)) 80 for (const Decl *D : DC->decls()) 81 handleDecl(D); 82 } 83 84 void MapExtDefNamesConsumer::addIfInMain(const DeclaratorDecl *DD, 85 SourceLocation defStart) { 86 std::optional<std::string> LookupName = 87 CrossTranslationUnitContext::getLookupName(DD); 88 if (!LookupName) 89 return; 90 assert(!LookupName->empty() && "Lookup name should be non-empty."); 91 92 if (CurrentFileName.empty()) { 93 CurrentFileName = std::string( 94 SM.getFileEntryForID(SM.getMainFileID())->tryGetRealPathName()); 95 if (CurrentFileName.empty()) 96 CurrentFileName = "invalid_file"; 97 } 98 99 switch (DD->getLinkageInternal()) { 100 case ExternalLinkage: 101 case VisibleNoLinkage: 102 case UniqueExternalLinkage: 103 if (SM.isInMainFile(defStart)) 104 Index[*LookupName] = CurrentFileName; 105 break; 106 default: 107 break; 108 } 109 } 110 111 class MapExtDefNamesAction : public ASTFrontendAction { 112 protected: 113 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 114 llvm::StringRef) override { 115 return std::make_unique<MapExtDefNamesConsumer>(CI.getASTContext()); 116 } 117 }; 118 119 static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); 120 121 static IntrusiveRefCntPtr<DiagnosticsEngine> Diags; 122 123 IntrusiveRefCntPtr<DiagnosticsEngine> GetDiagnosticsEngine() { 124 if (Diags) { 125 // Call reset to make sure we don't mix errors 126 Diags->Reset(false); 127 return Diags; 128 } 129 130 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); 131 TextDiagnosticPrinter *DiagClient = 132 new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts); 133 DiagClient->setPrefix("clang-extdef-mappping"); 134 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 135 136 IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine( 137 new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient)); 138 Diags.swap(DiagEngine); 139 140 // Retain this one time so it's not destroyed by ASTUnit::LoadFromASTFile 141 Diags->Retain(); 142 return Diags; 143 } 144 145 static CompilerInstance *CI = nullptr; 146 147 static bool HandleAST(StringRef AstPath) { 148 149 if (!CI) 150 CI = new CompilerInstance(); 151 152 IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine = GetDiagnosticsEngine(); 153 154 std::unique_ptr<ASTUnit> Unit = ASTUnit::LoadFromASTFile( 155 AstPath.str(), CI->getPCHContainerOperations()->getRawReader(), 156 ASTUnit::LoadASTOnly, DiagEngine, CI->getFileSystemOpts()); 157 158 if (!Unit) 159 return false; 160 161 FileManager FM(CI->getFileSystemOpts()); 162 SmallString<128> AbsPath(AstPath); 163 FM.makeAbsolutePath(AbsPath); 164 165 MapExtDefNamesConsumer Consumer = 166 MapExtDefNamesConsumer(Unit->getASTContext(), AbsPath); 167 Consumer.HandleTranslationUnit(Unit->getASTContext()); 168 169 return true; 170 } 171 172 static int HandleFiles(ArrayRef<std::string> SourceFiles, 173 CompilationDatabase &compilations) { 174 std::vector<std::string> SourcesToBeParsed; 175 176 // Loop over all input files, if they are pre-compiled AST 177 // process them directly in HandleAST, otherwise put them 178 // on a list for ClangTool to handle. 179 for (StringRef Src : SourceFiles) { 180 if (Src.endswith(".ast")) { 181 if (!HandleAST(Src)) { 182 return 1; 183 } 184 } else { 185 SourcesToBeParsed.push_back(Src.str()); 186 } 187 } 188 189 if (!SourcesToBeParsed.empty()) { 190 ClangTool Tool(compilations, SourcesToBeParsed); 191 return Tool.run(newFrontendActionFactory<MapExtDefNamesAction>().get()); 192 } 193 194 return 0; 195 } 196 197 int main(int argc, const char **argv) { 198 // Print a stack trace if we signal out. 199 sys::PrintStackTraceOnErrorSignal(argv[0], false); 200 PrettyStackTraceProgram X(argc, argv); 201 202 const char *Overview = "\nThis tool collects the USR name and location " 203 "of external definitions in the source files " 204 "(excluding headers).\n" 205 "Input can be either source files that are compiled " 206 "with compile database or .ast files that are " 207 "created from clang's -emit-ast option.\n"; 208 auto ExpectedParser = CommonOptionsParser::create( 209 argc, argv, ClangExtDefMapGenCategory, cl::ZeroOrMore, Overview); 210 if (!ExpectedParser) { 211 llvm::errs() << ExpectedParser.takeError(); 212 return 1; 213 } 214 CommonOptionsParser &OptionsParser = ExpectedParser.get(); 215 216 return HandleFiles(OptionsParser.getSourcePathList(), 217 OptionsParser.getCompilations()); 218 } 219