1//===-- lldb-rpc-gen.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#include "RPCCommon.h"
10#include "RPCServerHeaderEmitter.h"
11#include "RPCServerSourceEmitter.h"
12
13#include "clang/AST/AST.h"
14#include "clang/AST/ASTConsumer.h"
15#include "clang/AST/ASTContext.h"
16#include "clang/AST/RecursiveASTVisitor.h"
17#include "clang/Basic/SourceManager.h"
18#include "clang/CodeGen/ObjectFilePCHContainerWriter.h"
19#include "clang/Frontend/CompilerInstance.h"
20#include "clang/Frontend/FrontendAction.h"
21#include "clang/Frontend/FrontendActions.h"
22#include "clang/Serialization/ObjectFilePCHContainerReader.h"
23#include "clang/Tooling/CommonOptionsParser.h"
24#include "clang/Tooling/Tooling.h"
25
26#include "llvm/ADT/StringRef.h"
27#include "llvm/Support/CommandLine.h"
28#include "llvm/Support/Path.h"
29#include "llvm/Support/ToolOutputFile.h"
30#include "llvm/Support/raw_ostream.h"
31
32using namespace clang;
33using namespace clang::driver;
34using namespace clang::tooling;
35
36static llvm::cl::OptionCategory RPCGenCategory("Tool for generating LLDBRPC");
37
38static llvm::cl::opt<std::string>
39 OutputDir("output-dir",
40 llvm::cl::desc("Directory to output generated files to"),
41 llvm::cl::init(Val: ""), llvm::cl::cat(RPCGenCategory));
42
43static std::string GetLibraryOutputDirectory() {
44 llvm::SmallString<128> Path(OutputDir.getValue());
45 llvm::sys::path::append(path&: Path, a: "lib");
46 return std::string(Path);
47}
48
49static std::unique_ptr<llvm::ToolOutputFile>
50CreateOutputFile(llvm::StringRef OutputDir, llvm::StringRef Filename) {
51 llvm::SmallString<128> Path(OutputDir);
52 llvm::sys::path::append(path&: Path, a: Filename);
53
54 std::error_code EC;
55 auto OutputFile =
56 std::make_unique<llvm::ToolOutputFile>(args&: Path, args&: EC, args: llvm::sys::fs::OF_None);
57 if (EC) {
58 llvm::errs() << "Failed to create output file: " << Path << "!\n";
59 return nullptr;
60 }
61 return OutputFile;
62}
63
64struct GeneratedByproducts {
65 std::set<std::string> ClassNames;
66 std::set<std::string> MangledMethodNames;
67 std::set<std::string> SkippedMethodNames;
68 std::set<lldb_rpc_gen::Method> CallbackMethods;
69};
70
71enum SupportLevel {
72 eUnsupported,
73 eUnimplemented,
74 eImplemented,
75};
76
77class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
78public:
79 SBVisitor(GeneratedByproducts &Byproducts, SourceManager &Manager,
80 ASTContext &Context,
81 std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,
82 std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile)
83 : Byproducts(Byproducts), Manager(Manager), Context(Context),
84 ServerSourceEmitter(std::move(ServerMethodOutputFile)),
85 ServerHeaderEmitter(std::move(ServerHeaderOutputFile)) {}
86
87 ~SBVisitor() {}
88
89 bool VisitCXXRecordDecl(CXXRecordDecl *RDecl) {
90 if (ShouldSkipRecord(Decl: RDecl))
91 return true;
92
93 const std::string ClassName = RDecl->getNameAsString();
94 Byproducts.ClassNames.insert(x: ClassName);
95
96 // Print 'bool' instead of '_Bool'.
97 PrintingPolicy Policy(Context.getLangOpts());
98 Policy.Bool = true;
99
100 for (CXXMethodDecl *MDecl : RDecl->methods()) {
101 const std::string MangledName =
102 lldb_rpc_gen::GetMangledName(Context, MDecl);
103 const bool IsDisallowed = lldb_rpc_gen::MethodIsDisallowed(MangledName);
104 const bool HasCallbackParameter =
105 lldb_rpc_gen::HasCallbackParameter(MDecl);
106 SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl);
107 if (MethodSupportLevel == eImplemented && !IsDisallowed) {
108 const lldb_rpc_gen::Method Method(MDecl, Policy, Context);
109 ServerSourceEmitter.EmitMethod(Method);
110 ServerHeaderEmitter.EmitMethod(Method);
111 Byproducts.MangledMethodNames.insert(x: MangledName);
112 } else if (MethodSupportLevel == eUnimplemented)
113 Byproducts.SkippedMethodNames.insert(x: MangledName);
114 }
115 return true;
116 }
117
118private:
119 /// Determines whether we should skip a RecordDecl.
120 /// Conditions for skipping:
121 /// - Anything not in the header itself
122 /// - Certain inconvenient classes
123 /// - Records without definitions (forward declarations)
124 bool ShouldSkipRecord(CXXRecordDecl *Decl) {
125 const Type *DeclType = Decl->getTypeForDecl();
126 QualType CanonicalType = DeclType->getCanonicalTypeInternal();
127 return !Manager.isInMainFile(Decl->getBeginLoc()) ||
128 !Decl->hasDefinition() || Decl->getDefinition() != Decl ||
129 lldb_rpc_gen::TypeIsDisallowedClass(CanonicalType);
130 }
131
132 /// Check the support level for a type
133 /// Known unsupported types:
134 /// - FILE * (We do not want to expose this primitive)
135 /// - Types that are internal to LLDB
136 SupportLevel GetTypeSupportLevel(QualType Type) {
137 const std::string TypeName = Type.getAsString();
138 if (TypeName == "FILE *" || lldb_rpc_gen::TypeIsFromLLDBPrivate(Type))
139 return eUnsupported;
140
141 if (lldb_rpc_gen::TypeIsDisallowedClass(Type))
142 return eUnsupported;
143
144 return eImplemented;
145 }
146
147 /// Determine the support level of a given method.
148 /// Known unsupported methods:
149 /// - Non-public methods (lldb-rpc is a client and can only see public
150 /// things)
151 /// - Copy assignment operators (the client side will handle this)
152 /// - Move assignment operators (the client side will handle this)
153 /// - Methods involving unsupported types.
154 /// Known unimplemented methods:
155 /// - No variadic functions, e.g. Printf
156 SupportLevel GetMethodSupportLevel(CXXMethodDecl *MDecl) {
157 AccessSpecifier AS = MDecl->getAccess();
158 if (AS != AccessSpecifier::AS_public)
159 return eUnsupported;
160 if (MDecl->isCopyAssignmentOperator())
161 return eUnsupported;
162 if (MDecl->isMoveAssignmentOperator())
163 return eUnsupported;
164
165 if (MDecl->isVariadic())
166 return eUnimplemented;
167
168 SupportLevel ReturnTypeLevel = GetTypeSupportLevel(Type: MDecl->getReturnType());
169 if (ReturnTypeLevel != eImplemented)
170 return ReturnTypeLevel;
171
172 for (auto *ParamDecl : MDecl->parameters()) {
173 SupportLevel ParamTypeLevel = GetTypeSupportLevel(Type: ParamDecl->getType());
174 if (ParamTypeLevel != eImplemented)
175 return ParamTypeLevel;
176 }
177
178 // FIXME: If a callback does not take a `void *baton` parameter, it is
179 // considered unsupported at this time. On the server-side, we hijack the
180 // baton argument in order to pass additional information to the server-side
181 // callback so we can correctly perform a reverse RPC call back to the
182 // client. Without this baton, we would need the server-side callback to
183 // have some side channel by which it obtained that information, and
184 // spending time designing that doesn't outweight the cost of doing it at
185 // the moment.
186 bool HasCallbackParameter = false;
187 bool HasBatonParameter = false;
188 auto End = MDecl->parameters().end();
189 for (auto Iter = MDecl->parameters().begin(); Iter != End; Iter++) {
190 if ((*Iter)->getType()->isFunctionPointerType()) {
191 HasCallbackParameter = true;
192 continue;
193 }
194
195 // FIXME: We assume that if we have a function pointer and a void pointer
196 // together in the same parameter list, that it is not followed by a
197 // length argument. If that changes, we will need to revisit this
198 // implementation.
199 if ((*Iter)->getType()->isVoidPointerType())
200 HasBatonParameter = true;
201 }
202
203 if (HasCallbackParameter && !HasBatonParameter)
204 return eUnimplemented;
205
206 return eImplemented;
207 }
208
209 GeneratedByproducts &Byproducts;
210 SourceManager &Manager;
211 ASTContext &Context;
212 lldb_rpc_gen::RPCServerSourceEmitter ServerSourceEmitter;
213 lldb_rpc_gen::RPCServerHeaderEmitter ServerHeaderEmitter;
214};
215
216class SBConsumer : public ASTConsumer {
217public:
218 SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager,
219 ASTContext &Context,
220 std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,
221 std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile)
222 : Visitor(Byproducts, Manager, Context, std::move(ServerMethodOutputFile),
223 std::move(ServerHeaderOutputFile)) {}
224 bool HandleTopLevelDecl(DeclGroupRef DR) override {
225 for (Decl *D : DR)
226 Visitor.TraverseDecl(D);
227
228 return true;
229 }
230
231private:
232 SBVisitor Visitor;
233};
234
235class SBAction : public ASTFrontendAction {
236public:
237 SBAction(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {}
238
239 std::unique_ptr<ASTConsumer>
240 CreateASTConsumer(CompilerInstance &CI, llvm::StringRef File) override {
241 llvm::StringRef FilenameNoExt =
242 llvm::sys::path::stem(path: llvm::sys::path::filename(path: File));
243
244 const std::string ServerMethodFilename =
245 "Server_" + FilenameNoExt.str() + ".cpp";
246 std::unique_ptr<llvm::ToolOutputFile> ServerMethodOutputFile =
247 CreateOutputFile(OutputDir: GetServerOutputDirectory(), Filename: ServerMethodFilename);
248 if (!ServerMethodOutputFile)
249 return nullptr;
250
251 const std::string ServerHeaderFilename =
252 "Server_" + FilenameNoExt.str() + ".h";
253 std::unique_ptr<llvm::ToolOutputFile> ServerHeaderOutputFile =
254 CreateOutputFile(OutputDir: GetServerOutputDirectory(), Filename: ServerHeaderFilename);
255 if (!ServerHeaderOutputFile)
256 return nullptr;
257
258 ServerMethodOutputFile->keep();
259 ServerHeaderOutputFile->keep();
260 return std::make_unique<SBConsumer>(
261 args&: Byproducts, args&: CI.getSourceManager(), args&: CI.getASTContext(),
262 args: std::move(ServerMethodOutputFile), args: std::move(ServerHeaderOutputFile));
263 }
264
265private:
266 GeneratedByproducts &Byproducts;
267};
268
269class SBActionFactory : public FrontendActionFactory {
270public:
271 SBActionFactory(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {}
272
273 std::unique_ptr<FrontendAction> create() override {
274 return std::make_unique<SBAction>(args&: Byproducts);
275 }
276
277private:
278 GeneratedByproducts &Byproducts;
279};
280
281bool EmitAmalgamatedServerHeader(const std::vector<std::string> &Files) {
282 // Create the file
283 static constexpr llvm::StringLiteral AmalgamatedServerHeaderName = "SBAPI.h";
284 std::unique_ptr<llvm::ToolOutputFile> AmalgamatedServerHeader =
285 CreateOutputFile(OutputDir: GetServerOutputDirectory(), Filename: AmalgamatedServerHeaderName);
286 if (!AmalgamatedServerHeader)
287 return false;
288
289 // Write the header
290 AmalgamatedServerHeader->os()
291 << "#ifndef GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";
292 AmalgamatedServerHeader->os()
293 << "#define GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";
294 for (const auto &File : Files) {
295 llvm::StringRef FilenameNoExt =
296 llvm::sys::path::stem(path: llvm::sys::path::filename(path: File));
297 const std::string ServerHeaderFilename =
298 "Server_" + FilenameNoExt.str() + ".h";
299
300 AmalgamatedServerHeader->os()
301 << "#include \"" + ServerHeaderFilename + "\"\n";
302 }
303 AmalgamatedServerHeader->os() << "#include \"SBAPIExtensions.h\"\n";
304 AmalgamatedServerHeader->os()
305 << "#endif // GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";
306 AmalgamatedServerHeader->keep();
307 return true;
308}
309
310bool EmitClassNamesFile(std::set<std::string> &ClassNames) {
311 static constexpr llvm::StringLiteral ClassNamesFileName = "SBClasses.def";
312 std::unique_ptr<llvm::ToolOutputFile> ClassNamesFile =
313 CreateOutputFile(OutputDir: OutputDir.getValue(), Filename: ClassNamesFileName);
314 if (!ClassNamesFile)
315 return false;
316
317 ClassNamesFile->os() << "#ifndef SBCLASS\n";
318 ClassNamesFile->os() << "#error \"SBClass must be defined\"\n";
319 ClassNamesFile->os() << "#endif\n";
320
321 for (const auto &ClassName : ClassNames) {
322 if (ClassName == "SBStream" || ClassName == "SBProgress")
323 ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_NONCOPYABLE)\n";
324 else if (ClassName == "SBReproducer")
325 ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_STATICONLY)\n";
326
327 ClassNamesFile->os() << "SBCLASS(" << ClassName << ")\n";
328 if (ClassName == "SBStream" || ClassName == "SBReproducer" ||
329 ClassName == "SBProgress")
330 ClassNamesFile->os() << "#endif\n";
331 }
332 ClassNamesFile->keep();
333 return true;
334}
335
336bool EmitMethodNamesFile(std::set<std::string> &MangledMethodNames) {
337 static constexpr llvm::StringLiteral MethodNamesFileName = "SBAPI.def";
338 std::unique_ptr<llvm::ToolOutputFile> MethodNamesFile =
339 CreateOutputFile(OutputDir: OutputDir.getValue(), Filename: MethodNamesFileName);
340 if (!MethodNamesFile)
341 return false;
342
343 MethodNamesFile->os() << "#ifndef GENERATE_SBAPI\n";
344 MethodNamesFile->os() << "#error \"GENERATE_SBAPI must be defined\"\n";
345 MethodNamesFile->os() << "#endif\n";
346
347 for (const auto &MangledName : MangledMethodNames) {
348 MethodNamesFile->os() << "GENERATE_SBAPI(" << MangledName << ")\n";
349 }
350 MethodNamesFile->keep();
351 return true;
352}
353
354bool EmitSkippedMethodsFile(std::set<std::string> &SkippedMethodNames) {
355 static constexpr llvm::StringLiteral FileName = "SkippedMethods.txt";
356 std::unique_ptr<llvm::ToolOutputFile> File =
357 CreateOutputFile(OutputDir: OutputDir.getValue(), Filename: FileName);
358 if (!File)
359 return false;
360
361 for (const auto &Skipped : SkippedMethodNames) {
362 File->os() << Skipped << "\n";
363 }
364 File->keep();
365 return true;
366}
367
368int main(int argc, const char *argv[]) {
369 auto ExpectedParser = CommonOptionsParser::create(
370 argc, argv, Category&: RPCGenCategory, OccurrencesFlag: llvm::cl::OneOrMore,
371 Overview: "Tool for generating LLDBRPC interfaces and implementations");
372
373 if (!ExpectedParser) {
374 llvm::errs() << ExpectedParser.takeError();
375 return 1;
376 }
377
378 if (OutputDir.empty()) {
379 llvm::errs() << "Please specify an output directory for the generated "
380 "files with --output-dir!\n";
381 return 1;
382 }
383
384 CommonOptionsParser &OP = ExpectedParser.get();
385 auto PCHOpts = std::make_shared<PCHContainerOperations>();
386 PCHOpts->registerWriter(Writer: std::make_unique<ObjectFilePCHContainerWriter>());
387 PCHOpts->registerReader(Reader: std::make_unique<ObjectFilePCHContainerReader>());
388
389 ClangTool T(OP.getCompilations(), OP.getSourcePathList(), PCHOpts);
390
391 if (!EmitAmalgamatedServerHeader(Files: OP.getSourcePathList())) {
392 llvm::errs() << "Failed to create amalgamated server header\n";
393 return 1;
394 }
395
396 GeneratedByproducts Byproducts;
397
398 SBActionFactory Factory(Byproducts);
399 auto Result = T.run(Action: &Factory);
400 if (!EmitClassNamesFile(ClassNames&: Byproducts.ClassNames)) {
401 llvm::errs() << "Failed to create SB Class file\n";
402 return 1;
403 }
404 if (!EmitMethodNamesFile(MangledMethodNames&: Byproducts.MangledMethodNames)) {
405 llvm::errs() << "Failed to create Method Names file\n";
406 return 1;
407 }
408 if (!EmitSkippedMethodsFile(SkippedMethodNames&: Byproducts.SkippedMethodNames)) {
409 llvm::errs() << "Failed to create Skipped Methods file\n";
410 return 1;
411 }
412
413 return Result;
414}
415

source code of lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp