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