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