| 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 | |
| 32 | using namespace clang; |
| 33 | using namespace clang::driver; |
| 34 | using namespace clang::tooling; |
| 35 | |
| 36 | static llvm::cl::OptionCategory RPCGenCategory("Tool for generating LLDBRPC" ); |
| 37 | |
| 38 | static 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 | |
| 43 | static 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 | |
| 49 | static std::unique_ptr<llvm::ToolOutputFile> |
| 50 | CreateOutputFile(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 | |
| 64 | struct 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 | |
| 71 | enum SupportLevel { |
| 72 | eUnsupported, |
| 73 | eUnimplemented, |
| 74 | eImplemented, |
| 75 | }; |
| 76 | |
| 77 | class SBVisitor : public RecursiveASTVisitor<SBVisitor> { |
| 78 | public: |
| 79 | SBVisitor(GeneratedByproducts &Byproducts, SourceManager &Manager, |
| 80 | ASTContext &Context, |
| 81 | std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile, |
| 82 | std::unique_ptr<llvm::ToolOutputFile> &&) |
| 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 | |
| 118 | private: |
| 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 ; |
| 214 | }; |
| 215 | |
| 216 | class SBConsumer : public ASTConsumer { |
| 217 | public: |
| 218 | SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager, |
| 219 | ASTContext &Context, |
| 220 | std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile, |
| 221 | std::unique_ptr<llvm::ToolOutputFile> &&) |
| 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 | |
| 231 | private: |
| 232 | SBVisitor Visitor; |
| 233 | }; |
| 234 | |
| 235 | class SBAction : public ASTFrontendAction { |
| 236 | public: |
| 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 = |
| 252 | "Server_" + FilenameNoExt.str() + ".h" ; |
| 253 | std::unique_ptr<llvm::ToolOutputFile> = |
| 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 | |
| 265 | private: |
| 266 | GeneratedByproducts &Byproducts; |
| 267 | }; |
| 268 | |
| 269 | class SBActionFactory : public FrontendActionFactory { |
| 270 | public: |
| 271 | SBActionFactory(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {} |
| 272 | |
| 273 | std::unique_ptr<FrontendAction> create() override { |
| 274 | return std::make_unique<SBAction>(args&: Byproducts); |
| 275 | } |
| 276 | |
| 277 | private: |
| 278 | GeneratedByproducts &Byproducts; |
| 279 | }; |
| 280 | |
| 281 | bool (const std::vector<std::string> &Files) { |
| 282 | // Create the file |
| 283 | static constexpr llvm::StringLiteral = "SBAPI.h" ; |
| 284 | std::unique_ptr<llvm::ToolOutputFile> = |
| 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 = |
| 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 | |
| 310 | bool 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 | |
| 336 | bool 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 | |
| 354 | bool 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 | |
| 368 | int 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 | |