================ @@ -0,0 +1,542 @@ +#include "RPCLibrarySourceEmitter.h" +#include "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "clang/Frontend/CompilerInstance.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" +#include <string> + +using namespace clang; +using namespace lldb_rpc_gen; + +static constexpr llvm::StringRef ReturnVariableName("__result"); + +// This map stores any method that needs custom logic with a struct that +// tells us where the logic needs to be inserted and what code needs to be +// inserted. The code here is stored as a raw string literal. +const llvm::StringMap<RPCLibrarySourceEmitter::CustomLogic> + CustomLogicForMethods = { + {"_ZN4lldb10SBDebugger6CreateEbPFvPKcPvES3_", + {RPCLibrarySourceEmitter::CustomLogicLocation::eAfterDecode, R"code( + // Now source the .lldbinit files manually since we can't rely on the + // LLDB.framework on the other side to have special support for sourcing the right file + // since it would try to source "~/.lldbinit-lldb-rpc-server" followed by + // "~/.lldbinit". We want it to try "~.lldbinit-%s" where %s is the + // current program basename followed by "~/.lldbinit". + + if (source_init_files && __result.ObjectRefIsValid()) { + const char *program_basename = rpc::GetProgramBasename(); + if (program_basename) { + char init_path[PATH_MAX]; + snprintf(init_path, sizeof(init_path), "~/.lldbinit-%s", + program_basename); + lldb_rpc::SBFileSpec program_init_file(connection, init_path, true); + if (program_init_file.Exists()) { + char command_str[PATH_MAX]; + snprintf(command_str, sizeof(command_str), + "command source -s 1 -c 1 -e 0 '%s'", init_path); + __result.HandleCommand(command_str); + } else { + __result.HandleCommand("command source -s 1 -c 1 -e 0 '~/.lldbinit'"); + } + } + })code"}}, +}; + +static std::string GetLocalObjectRefCtor(const std::string &ClassName) { + return "rpc::LocalObjectRef(LLDB_RPC_INVALID_CONNECTION_ID, eClass_lldb_" + + ClassName + ", LLDB_RPC_INVALID_OBJECT_ID)"; +} + +static std::string GetObjectRefCtor(const std::string &ClassName) { + return "rpc::ObjectRef(LLDB_RPC_INVALID_CONNECTION_ID, eClass_lldb_" + + ClassName + ", LLDB_RPC_INVALID_OBJECT_ID)"; +} + +void RPCLibrarySourceEmitter::EmitCopyCtor() { + EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass + + "(const lldb_rpc::" + CurrentClass + " &rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitCopyAssign() { + EmitLine("lldb_rpc::" + CurrentClass + " &lldb_rpc::" + CurrentClass + + "::operator=(const lldb_rpc::" + CurrentClass + " &rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitMoveCtor() { + EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass + + "(lldb_rpc::" + CurrentClass + " &&rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitMoveAssign() { + EmitLine("lldb_rpc::" + CurrentClass + " &lldb_rpc::" + CurrentClass + + "::operator=(lldb_rpc::" + CurrentClass + " &&rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitMethod(const Method &method) { + if (method.IsCopyCtor) { + CopyCtorEmitted = true; + EmitCopyCtor(); + return; + } else if (method.IsCopyAssign) { + CopyAssignEmitted = true; + EmitCopyAssign(); + return; + } else if (method.IsMoveCtor) { + MoveCtorEmitted = true; + EmitMoveCtor(); + return; + } else if (method.IsMoveAssign) { + MoveAssignEmitted = true; + EmitMoveAssign(); + return; + } + + EmitCommentHeader(method); + EmitFunctionHeader(method); + EmitFunctionBody(method); + EmitFunctionFooter(); +} + +void RPCLibrarySourceEmitter::StartClass(std::string ClassName) { + CurrentClass = std::move(ClassName); + if (lldb_rpc_gen::SBClassRequiresDefaultCtor(CurrentClass)) { + std::string BaseClassCtor = + lldb_rpc_gen::SBClassInheritsFromObjectRef(CurrentClass) + ? GetObjectRefCtor(CurrentClass) + : GetLocalObjectRefCtor(CurrentClass); + EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass + + "() : " + BaseClassCtor + " {}"); + } +} + +void RPCLibrarySourceEmitter::EndClass() { + if (lldb_rpc_gen::SBClassRequiresCopyCtorAssign(CurrentClass)) { + if (!CopyCtorEmitted) + EmitCopyCtor(); + + if (!CopyAssignEmitted) + EmitCopyAssign(); + } + + if (!MoveCtorEmitted) + EmitMoveCtor(); + + if (!MoveAssignEmitted) + EmitMoveAssign(); + + CopyCtorEmitted = false; + CopyAssignEmitted = false; + MoveCtorEmitted = false; + MoveAssignEmitted = false; +} + +void RPCLibrarySourceEmitter::EmitCommentHeader(const Method &method) { + std::string CommentLine; + llvm::raw_string_ostream CommentStream(CommentLine); + + CommentStream << "// " + << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.QualifiedName) + << "(" << method.CreateParamListAsString(eLibrary) << ")"; + if (method.IsConst) + CommentStream << " const"; + + EmitLine("//-----------------------------------------------------------"); + EmitLine(CommentLine); + EmitLine("//-----------------------------------------------------------"); +} + +void RPCLibrarySourceEmitter::EmitFunctionHeader(const Method &method) { + std::string FunctionHeader; + llvm::raw_string_ostream FunctionHeaderStream(FunctionHeader); + + if (!method.IsDtor && !method.IsConversionMethod && !method.IsCtor) + FunctionHeaderStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.ReturnType.getAsString(method.Policy)) + << " "; + + FunctionHeaderStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.QualifiedName) + << "(" << method.CreateParamListAsString(eLibrary) + << ")"; + if (method.IsConst) + FunctionHeaderStream << " const"; + if (method.IsCtor) { + if (lldb_rpc_gen::SBClassInheritsFromObjectRef(method.BaseName)) + FunctionHeaderStream << " : " << GetObjectRefCtor(method.BaseName); + else + FunctionHeaderStream << " : " << GetLocalObjectRefCtor(method.BaseName); + } + FunctionHeaderStream << " {"; + + EmitLine(FunctionHeader); + IndentLevel++; +} + +void RPCLibrarySourceEmitter::EmitFunctionBody(const Method &method) { + // There's nothing to do for destructors. The LocalObjectRef destructor should + // handle everything for us. + if (method.IsDtor) + return; + + EmitLine("// 1) Perform setup"); + EmitFunctionSetup(method); + EmitLine("// 2) Send RPC call"); + EmitSendRPCCall(method); + EmitLine("// 3) Decode return values"); + EmitDecodeReturnValues(method); +} + +void RPCLibrarySourceEmitter::EmitFunctionFooter() { + IndentLevel--; + EmitLine("}"); +} + +void RPCLibrarySourceEmitter::EmitFunctionSetup(const Method &method) { + if (!method.ReturnType->isVoidType()) + EmitReturnValueStorage(method); + + EmitConnectionSetup(method); + + EmitLine("// RPC Communication setup"); + EmitLine("static RPCFunctionInfo g_func(\"" + method.MangledName + "\");"); + EmitLine("RPCStream send;"); + EmitLine("RPCStream response;"); + EmitLine("g_func.Encode(send);"); + + EmitEncodeParameters(method); + + if (CustomLogicForMethods.lookup(method.MangledName).Location == + CustomLogicLocation::eAfterSetup) + EmitCustomLogic(method); +} + +void RPCLibrarySourceEmitter::EmitReturnValueStorage(const Method &method) { + assert(!method.ReturnType->isVoidType() && + "Cannot emit return value storage when return type is 'void'"); + + EmitLine("// Storage for return value"); + std::string ReturnValueStorage; + llvm::raw_string_ostream ReturnValueStorageStream(ReturnValueStorage); + + std::string ReturnValueType; + if (lldb_rpc_gen::TypeIsConstCharPtr(method.ReturnType)) + ReturnValueStorageStream << "rpc_common::ConstCharPointer " + << ReturnVariableName << ";"; + else if (method.ReturnType->isPointerType()) + ReturnValueStorageStream << "Bytes " << ReturnVariableName << ";"; + else { + // We need to get the unqualified type because we don't want the return + // variable to be marked `const`. That would prevent us from changing it + // during the decoding step. + QualType UnqualifiedReturnType = method.ReturnType.getUnqualifiedType(); + ReturnValueStorageStream + << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + UnqualifiedReturnType.getAsString(method.Policy)) + << " " << ReturnVariableName << " = {};"; + } + EmitLine(ReturnValueStorage); +} + +void RPCLibrarySourceEmitter::EmitConnectionSetup(const Method &method) { + // Methods know if they require a connection parameter. We need to figure out + // which scenario we're in. + bool ConnectionDerived = false; + if (!method.RequiresConnectionParameter()) { + // If we have an instance method that is not a constructor, we have a valid + // connection from `this` via `ObjectRefGetConnectionSP()`. + if (!method.IsCtor && method.IsInstance) { + EmitLine("// Deriving connection from this."); + EmitLine("rpc_common::ConnectionSP connection_sp = " + "ObjectRefGetConnectionSP();"); + ConnectionDerived = true; + } + + // Otherewise, we try to derive it from an existing parameter. + if (!ConnectionDerived) + for (const auto &Param : method.Params) { + if (lldb_rpc_gen::TypeIsSBClass(Param.Type)) { + EmitLine("// Deriving connection from SB class parameter."); + std::string ConnectionLine = + "rpc_common::ConnectionSP connection_sp = " + Param.Name; + if (Param.Type->isPointerType()) + ConnectionLine += "->"; + else + ConnectionLine += "."; + ConnectionLine += "ObjectRefGetConnectionSP();"; + EmitLine(ConnectionLine); + ConnectionDerived = true; + break; + } + } + } else { + // This method requires a connection parameter. It will always be named + // "connection" and it will always come first in the parameter list. + EmitLine("// Using connection parameter."); + EmitLine( + "rpc_common::ConnectionSP connection_sp = connection.GetConnection();"); + ConnectionDerived = true; + } + + assert(ConnectionDerived && + "Unable to determine where method should derive connection from"); + + // NOTE: By this point, we should have already emitted the storage for the + // return value. + std::string FailureReturnExpression; + if (method.ReturnType->isPointerType()) + FailureReturnExpression = "nullptr"; + else if (!method.ReturnType->isVoidType()) + FailureReturnExpression = "__result"; + EmitLine("if (!connection_sp) return " + FailureReturnExpression + ";"); +} + +void RPCLibrarySourceEmitter::EmitEncodeParameters(const Method &method) { + // Encode parameters. + if (method.IsInstance && !method.IsCtor) + EmitLine("RPCValueEncoder(send, " + "rpc_common::RPCPacket::ValueType::Argument, *this);"); + + for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) { + // SBTarget::BreakpointCreateByNames specifically uses an + // rpc_common::StringList when encoding the list of symbol names in the + // handwritten version. This allows it to account for null terminators and + // without it, Xcode crashes when calling this function. The else-if block + // below replaces params that have a pointer and length with a Bytes object. + // We can use the same logic in order to replace const char **s with + // StringLists + // + // rdar://146976130 + if (lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) && + Iter->IsFollowedByLen) { + std::string StringListLine; + const std::string StringListName = Iter->Name + "_list"; + StringListLine = "StringList " + StringListName + "(" + Iter->Name + ", "; + Iter++; + StringListLine += Iter->Name + ");"; + Iter--; + EmitLine(StringListLine); + EmitLine( + "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " + + StringListName + ");"); + // When we have pointer parameters, in general the strategy is + // to create `Bytes` objects from them (basically a buffer with a size) + // and then move those over the wire. We're not moving the pointer itself, + // but the contents of memory being pointed to. There are a few exceptions + // to this: + // - If the type is `const char *` or `const char **`, those are handled + // specially and can be encoded directly. + // - If we have a function pointer, we move the pointer value directly. + // To do the callback from the server-side, we will need this pointer + // value to correctly invoke the function client-side. + // - If we have a baton (to support a callback), we need to move the + // pointer value directly. This is for the same reason as callbacks + // above. + // - If we have a pointer to an SB class, we just dereference it and + // encode it like a normal SB class object. ---------------- chelcassanova wrote:
I'm actually going to move this comment to be inside of the else if block below and match it with the comment that's already in that block. https://github.com/llvm/llvm-project/pull/147655 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits