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