585 lines · cpp
1//===-- RPCServerSourceEmitter.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-exception6//7//===----------------------------------------------------------------------===//8 9#include "RPCServerSourceEmitter.h"10#include "RPCCommon.h"11 12#include "clang/AST/AST.h"13#include "clang/Frontend/CompilerInstance.h"14 15#include "llvm/ADT/StringExtras.h"16#include "llvm/ADT/StringRef.h"17#include "llvm/Support/ToolOutputFile.h"18#include "llvm/Support/raw_ostream.h"19 20#include <map>21 22using namespace clang;23using namespace lldb_rpc_gen;24 25// For methods with pointer return types, it's important that we know how big26// the type of the pointee is. We must correctly size a buffer (in the form of a27// Bytes object) before we can actually use it.28static const std::map<llvm::StringRef, size_t> MethodsWithPointerReturnTypes = {29 {"_ZN4lldb12SBModuleSpec12GetUUIDBytesEv", 16}, // sizeof(uuid_t) -> 1630 {"_ZNK4lldb8SBModule12GetUUIDBytesEv", 16}, // sizeof(uuid_t) -> 1631};32 33void RPCServerSourceEmitter::EmitMethod(const Method &method) {34 if (method.ContainsFunctionPointerParameter)35 EmitCallbackFunction(method);36 37 EmitCommentHeader(method);38 EmitFunctionHeader(method);39 EmitFunctionBody(method);40 EmitFunctionFooter();41}42 43void RPCServerSourceEmitter::EmitCommentHeader(const Method &method) {44 std::string CommentLine;45 llvm::raw_string_ostream CommentStream(CommentLine);46 47 CommentStream << "// " << method.QualifiedName << "("48 << method.CreateParamListAsString(eServer) << ")";49 if (method.IsConst)50 CommentStream << " const";51 52 EmitLine("//------------------------------------------------------------");53 EmitLine(CommentLine);54 EmitLine("//------------------------------------------------------------");55}56 57void RPCServerSourceEmitter::EmitFunctionHeader(const Method &method) {58 std::string FunctionHeader;59 llvm::raw_string_ostream FunctionHeaderStream(FunctionHeader);60 FunctionHeaderStream61 << "bool rpc_server::" << method.MangledName62 << "::HandleRPCCall(rpc_common::Connection &connection, RPCStream "63 "&send, RPCStream &response) {";64 EmitLine(FunctionHeader);65 IndentLevel++;66}67 68void RPCServerSourceEmitter::EmitFunctionBody(const Method &method) {69 EmitLine("// 1) Make local storage for incoming function arguments");70 EmitStorageForParameters(method);71 EmitLine("// 2) Decode all function arguments");72 EmitDecodeForParameters(method);73 EmitLine("// 3) Call the method and encode the return value");74 EmitMethodCallAndEncode(method);75}76 77void RPCServerSourceEmitter::EmitFunctionFooter() {78 EmitLine("return true;");79 IndentLevel--;80 EmitLine("}");81}82 83void RPCServerSourceEmitter::EmitStorageForParameters(const Method &method) {84 // If we have an instance method and it isn't a constructor, we'll need to85 // emit a "this" pointer.86 if (method.IsInstance && !method.IsCtor)87 EmitStorageForOneParameter(method.ThisType, "this_ptr", method.Policy,88 /* IsFollowedByLen = */ false);89 for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) {90 EmitStorageForOneParameter(Iter->Type, Iter->Name, method.Policy,91 Iter->IsFollowedByLen);92 // Skip over the length parameter, we don't emit it.93 if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&94 Iter->IsFollowedByLen)95 Iter++;96 }97}98 99void RPCServerSourceEmitter::EmitStorageForOneParameter(100 QualType ParamType, const std::string &ParamName,101 const PrintingPolicy &Policy, bool IsFollowedByLen) {102 // First, we consider `const char *`, `const char **`. They have special103 // server-side types.104 if (TypeIsConstCharPtr(ParamType)) {105 EmitLine("rpc_common::ConstCharPointer " + ParamName + ";");106 return;107 } else if (TypeIsConstCharPtrPtr(ParamType)) {108 EmitLine("rpc_common::StringList " + ParamName + ";");109 return;110 }111 112 QualType UnderlyingType =113 lldb_rpc_gen::GetUnqualifiedUnderlyingType(ParamType);114 const bool IsSBClass = lldb_rpc_gen::TypeIsSBClass(UnderlyingType);115 116 if (ParamType->isPointerType() && !IsSBClass) {117 // Void pointer with no length is usually a baton for a callback. We're118 // going to hold onto the pointer value so we can send it back to the119 // client-side when we implement callbacks.120 if (ParamType->isVoidPointerType() && !IsFollowedByLen) {121 EmitLine("void * " + ParamName + " = nullptr;");122 return;123 }124 125 if (!ParamType->isFunctionPointerType()) {126 EmitLine("Bytes " + ParamName + ";");127 return;128 }129 130 assert(ParamType->isFunctionPointerType() && "Unhandled pointer type");131 EmitLine("rpc_common::function_ptr_t " + ParamName + " = nullptr;");132 return;133 }134 135 std::string StorageDeclaration;136 llvm::raw_string_ostream StorageDeclarationStream(StorageDeclaration);137 138 UnderlyingType.print(StorageDeclarationStream, Policy);139 StorageDeclarationStream << " ";140 if (IsSBClass)141 StorageDeclarationStream << "*";142 StorageDeclarationStream << ParamName;143 if (IsSBClass)144 StorageDeclarationStream << " = nullptr";145 else146 StorageDeclarationStream << " = {}";147 StorageDeclarationStream << ";";148 EmitLine(StorageDeclaration);149}150 151void RPCServerSourceEmitter::EmitDecodeForParameters(const Method &method) {152 if (method.IsInstance && !method.IsCtor)153 EmitDecodeForOneParameter(method.ThisType, "this_ptr", method.Policy);154 for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) {155 EmitDecodeForOneParameter(Iter->Type, Iter->Name, method.Policy);156 if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&157 Iter->IsFollowedByLen)158 Iter++;159 }160}161 162void RPCServerSourceEmitter::EmitDecodeForOneParameter(163 QualType ParamType, const std::string &ParamName,164 const PrintingPolicy &Policy) {165 QualType UnderlyingType =166 lldb_rpc_gen::GetUnqualifiedUnderlyingType(ParamType);167 168 if (TypeIsSBClass(UnderlyingType)) {169 std::string DecodeLine;170 llvm::raw_string_ostream DecodeLineStream(DecodeLine);171 DecodeLineStream << ParamName << " = "172 << "RPCServerObjectDecoder<";173 UnderlyingType.print(DecodeLineStream, Policy);174 DecodeLineStream << ">(send, rpc_common::RPCPacket::ValueType::Argument);";175 EmitLine(DecodeLine);176 EmitLine("if (!" + ParamName + ")");177 IndentLevel++;178 EmitLine("return false;");179 IndentLevel--;180 } else {181 EmitLine("if (!RPCValueDecoder(send, "182 "rpc_common::RPCPacket::ValueType::Argument, " +183 ParamName + "))");184 IndentLevel++;185 EmitLine("return false;");186 IndentLevel--;187 }188}189 190std::string RPCServerSourceEmitter::CreateMethodCall(const Method &method) {191 std::string MethodCall;192 llvm::raw_string_ostream MethodCallStream(MethodCall);193 if (method.IsInstance) {194 if (!method.IsCtor)195 MethodCallStream << "this_ptr->";196 MethodCallStream << method.BaseName;197 } else198 MethodCallStream << method.QualifiedName;199 200 std::vector<std::string> Args;201 std::string FunctionPointerName;202 for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) {203 std::string Arg;204 // We must check for `const char *` and `const char **` first.205 if (TypeIsConstCharPtr(Iter->Type)) {206 // `const char *` is stored server-side as rpc_common::ConstCharPointer207 Arg = Iter->Name + ".c_str()";208 } else if (TypeIsConstCharPtrPtr(Iter->Type)) {209 // `const char **` is stored server-side as rpc_common::StringList210 Arg = Iter->Name + ".argv()";211 } else if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) {212 Arg = Iter->Name;213 if (!Iter->Type->isPointerType())214 Arg = "*" + Iter->Name;215 } else if (Iter->Type->isPointerType() &&216 !Iter->Type->isFunctionPointerType() &&217 (!Iter->Type->isVoidPointerType() || Iter->IsFollowedByLen)) {218 // We move pointers between the server and client as 'Bytes' objects.219 // Pointers with length arguments will have their length filled in below.220 // Pointers with no length arguments are assumed to behave like an array221 // with length of 1, except for void pointers which are handled222 // differently.223 Arg = "(" + Iter->Type.getAsString(method.Policy) + ")" + Iter->Name +224 ".GetData()";225 } else if (Iter->Type->isFunctionPointerType()) {226 // If we have a function pointer, we only want to pass something along if227 // we got a real pointer.228 Arg = Iter->Name + " ? " + method.MangledName + "_callback : nullptr";229 FunctionPointerName = Iter->Name;230 } else if (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen &&231 method.ContainsFunctionPointerParameter) {232 // Assumptions:233 // - This is assumed to be the baton for the function pointer.234 // - This is assumed to come after the function pointer parameter.235 // We always produce this regardless of the value of the baton argument.236 Arg = "new CallbackInfo(" + FunctionPointerName + ", " + Iter->Name +237 ", connection.GetConnectionID())";238 } else239 Arg = Iter->Name;240 241 if (Iter->Type->isRValueReferenceType())242 Arg = "std::move(" + Arg + ")";243 Args.push_back(Arg);244 245 if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&246 Iter->IsFollowedByLen) {247 std::string LengthArg = Iter->Name + ".GetSize()";248 if (!Iter->Type->isVoidPointerType()) {249 QualType UUT = lldb_rpc_gen::GetUnqualifiedUnderlyingType(Iter->Type);250 LengthArg += " / sizeof(" + UUT.getAsString(method.Policy) + ")";251 }252 Args.push_back(LengthArg);253 Iter++;254 }255 }256 MethodCallStream << "(" << llvm::join(Args, ", ") << ")";257 258 return MethodCall;259}260 261std::string RPCServerSourceEmitter::CreateEncodeLine(const std::string &Value,262 bool IsEncodingSBClass) {263 std::string EncodeLine;264 llvm::raw_string_ostream EncodeLineStream(EncodeLine);265 266 if (IsEncodingSBClass)267 EncodeLineStream << "RPCServerObjectEncoder(";268 else269 EncodeLineStream << "RPCValueEncoder(";270 271 EncodeLineStream272 << "response, rpc_common::RPCPacket::ValueType::ReturnValue, ";273 EncodeLineStream << Value;274 EncodeLineStream << ");";275 return EncodeLine;276}277 278// There are 4 cases to consider:279// - const SBClass &: No need to do anything.280// - const foo &: No need to do anything.281// - SBClass &: The server and the client hold on to IDs to refer to specific282// instances, so there's no need to send any information back to the client.283// - foo &: The client is sending us a value over the wire, but because the type284// is mutable, we must send the changed value back in case the method call285// mutated it.286//287// Updating a mutable reference is done as a return value from the RPC288// perspective. These return values need to be emitted after the method's return289// value, and they are emitted in the order in which they occur in the290// declaration.291void RPCServerSourceEmitter::EmitEncodesForMutableParameters(292 const std::vector<Param> &Params) {293 for (auto Iter = Params.begin(); Iter != Params.end(); Iter++) {294 // No need to manually update an SBClass295 if (lldb_rpc_gen::TypeIsSBClass(Iter->Type))296 continue;297 298 if (!Iter->Type->isReferenceType() && !Iter->Type->isPointerType())299 continue;300 301 // If we have a void pointer with no length, there's nothing to update. This302 // is likely a baton for a callback. The same goes for function pointers.303 if (Iter->Type->isFunctionPointerType() ||304 (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen))305 continue;306 307 // No need to update pointers and references to const-qualified data.308 QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type);309 if (UnderlyingType.isConstQualified())310 continue;311 312 const std::string EncodeLine =313 CreateEncodeLine(Iter->Name, /* IsEncodingSBClass = */ false);314 EmitLine(EncodeLine);315 }316}317 318// There are 3 possible scenarios that this method can encounter:319// 1. The method has no return value and is not a constructor.320// Only the method call itself is emitted.321// 2. The method is a constructor.322// The call to the constructor is emitted in the encode line.323// 3. The method has a return value.324// The method call is emitted and the return value is captured in a variable.325// After that, an encode call is emitted with the variable that captured the326// return value.327void RPCServerSourceEmitter::EmitMethodCallAndEncode(const Method &method) {328 const std::string MethodCall = CreateMethodCall(method);329 330 // If this function returns nothing, we just emit the call and update any331 // mutable references. Note that constructors have return type `void` so we332 // must explicitly check for that here.333 if (!method.IsCtor && method.ReturnType->isVoidType()) {334 EmitLine(MethodCall + ";");335 EmitEncodesForMutableParameters(method.Params);336 return;337 }338 339 static constexpr llvm::StringLiteral ReturnVariableName("__result");340 341 // If this isn't a constructor, we'll need to store the result of the method342 // call in a result variable.343 if (!method.IsCtor) {344 // We need to determine what the appropriate return type is. Here is the345 // strategy:346 // 1.) `SBFoo` -> `SBFoo &&`347 // 2.) If the type is a pointer other than `const char *` or `const char **`348 // or `void *`, the return type will be `Bytes` (e.g. `const uint8_t *`349 // -> `Bytes`).350 // 3.) Otherwise, emit the exact same return type.351 std::string ReturnTypeName;352 std::string AssignLine;353 llvm::raw_string_ostream AssignLineStream(AssignLine);354 if (method.ReturnType->isPointerType() &&355 !lldb_rpc_gen::TypeIsConstCharPtr(method.ReturnType) &&356 !lldb_rpc_gen::TypeIsConstCharPtrPtr(method.ReturnType) &&357 !method.ReturnType->isVoidPointerType()) {358 llvm::StringRef MangledNameRef(method.MangledName);359 auto Pos = MethodsWithPointerReturnTypes.find(MangledNameRef);360 assert(Pos != MethodsWithPointerReturnTypes.end() &&361 "Unable to determine the size of the return buffer");362 if (Pos == MethodsWithPointerReturnTypes.end()) {363 EmitLine(364 "// Intentionally inserting a compiler error. lldb-rpc-gen "365 "was unable to determine how large the return buffer should be.");366 EmitLine("#error: \"unable to determine size of return buffer\"");367 return;368 }369 AssignLineStream << "Bytes " << ReturnVariableName << "(" << MethodCall370 << ", " << Pos->second << ");";371 } else {372 if (lldb_rpc_gen::TypeIsSBClass(method.ReturnType)) {373 // We want to preserve constness, so we don't strip qualifications from374 // the underlying type375 QualType UnderlyingReturnType =376 lldb_rpc_gen::GetUnderlyingType(method.ReturnType);377 ReturnTypeName =378 UnderlyingReturnType.getAsString(method.Policy) + " &&";379 } else380 ReturnTypeName = method.ReturnType.getAsString(method.Policy);381 382 AssignLineStream << ReturnTypeName << " " << ReturnVariableName << " = "383 << MethodCall << ";";384 }385 EmitLine(AssignLine);386 }387 388 const bool IsEncodingSBClass =389 lldb_rpc_gen::TypeIsSBClass(method.ReturnType) || method.IsCtor;390 391 std::string ValueToEncode;392 if (IsEncodingSBClass) {393 if (method.IsCtor)394 ValueToEncode = MethodCall;395 else396 ValueToEncode = "std::move(" + ReturnVariableName.str() + ")";397 } else398 ValueToEncode = ReturnVariableName.str();399 400 const std::string ReturnValueEncodeLine =401 CreateEncodeLine(ValueToEncode, IsEncodingSBClass);402 EmitLine(ReturnValueEncodeLine);403 EmitEncodesForMutableParameters(method.Params);404}405 406// NOTE: This contains most of the same knowledge as RPCLibrarySourceEmitter. I407// have chosen not to re-use code here because the needs are different enough408// that it would be more work to re-use than just reimplement portions of it.409// Specifically:410// - Callbacks do not neatly fit into a `Method` object, which currently411// assumes that you have a CXXMethodDecl (We have a FunctionDecl at most).412// - We only generate callbacks that have a `void *` baton parameter. We hijack413// those baton parameters and treat them differently.414// - Callbacks need to do something special for moving SB class references back415// to the client-side.416void RPCServerSourceEmitter::EmitCallbackFunction(const Method &method) {417 // Check invariants and locate necessary resources418 Param FuncPointerParam;419 Param BatonParam;420 for (const auto &Param : method.Params)421 if (Param.Type->isFunctionPointerType())422 FuncPointerParam = Param;423 else if (Param.Type->isVoidPointerType())424 BatonParam = Param;425 426 assert(FuncPointerParam.Type->isFunctionPointerType() &&427 "Emitting callback function with no function pointer");428 assert(BatonParam.Type->isVoidPointerType() &&429 "Emitting callback function with no baton");430 431 QualType FuncType = FuncPointerParam.Type->getPointeeType();432 const auto *FuncProtoType = FuncType->getAs<FunctionProtoType>();433 assert(FuncProtoType && "Emitting callback with no parameter information");434 if (!FuncProtoType)435 return; // If asserts are off, we'll just fail to compile.436 437 std::vector<Param> CallbackParams;438 std::vector<std::string> CallbackParamsAsStrings;439 uint8_t ArgIdx = 0;440 for (QualType ParamType : FuncProtoType->param_types()) {441 Param CallbackParam;442 CallbackParam.IsFollowedByLen = false;443 CallbackParam.Type = ParamType;444 if (ParamType->isVoidPointerType())445 CallbackParam.Name = "baton";446 else447 CallbackParam.Name = "arg" + std::to_string(ArgIdx++);448 449 CallbackParams.push_back(CallbackParam);450 CallbackParamsAsStrings.push_back(ParamType.getAsString(method.Policy) +451 " " + CallbackParam.Name);452 }453 const std::string CallbackReturnTypeName =454 FuncProtoType->getReturnType().getAsString(method.Policy);455 const std::string CallbackName = method.MangledName + "_callback";456 457 // Emit Function Header458 std::string Header;459 llvm::raw_string_ostream HeaderStream(Header);460 HeaderStream << "static " << CallbackReturnTypeName << " " << CallbackName461 << "(" << llvm::join(CallbackParamsAsStrings, ", ") << ") {";462 EmitLine(Header);463 IndentLevel++;464 465 // Emit Function Body466 EmitLine("// RPC connection setup and sanity checking");467 EmitLine("CallbackInfo *callback_info = (CallbackInfo *)baton;");468 EmitLine("rpc_common::ConnectionSP connection_sp = "469 "rpc_common::Connection::GetConnectionFromID(callback_info->"470 "connection_id);");471 EmitLine("if (!connection_sp)");472 IndentLevel++;473 if (FuncProtoType->getReturnType()->isVoidType())474 EmitLine("return;");475 else476 EmitLine("return {};");477 IndentLevel--;478 479 EmitLine("// Preparing to make the call");480 EmitLine("static RPCFunctionInfo g_func(\"" + CallbackName + "\");");481 EmitLine("RPCStream send;");482 EmitLine("RPCStream response;");483 EmitLine("g_func.Encode(send);");484 485 EmitLine("// The first thing we encode is the callback address so that the "486 "client-side can know where the callback is");487 EmitLine("RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, "488 "callback_info->callback);");489 EmitLine("// Encode all the arguments");490 for (const Param &CallbackParam : CallbackParams) {491 if (lldb_rpc_gen::TypeIsSBClass(CallbackParam.Type)) {492 493 // FIXME: SB class server references are stored as non-const references so494 // that we can actually change them as needed. If a parameter is marked495 // const, we will fail to compile because we cannot make an496 // SBFooServerReference from a `const SBFoo &`.497 // To work around this issue, we'll apply a `const_cast` if needed so we498 // can continue to generate callbacks for now, but we really should499 // rethink the way we store object IDs server-side to support500 // const-qualified parameters.501 QualType UnderlyingSBClass =502 lldb_rpc_gen::GetUnderlyingType(CallbackParam.Type);503 QualType UnqualifiedUnderlyingSBClass =504 UnderlyingSBClass.getUnqualifiedType();505 506 std::string SBClassName = GetSBClassNameFromType(UnderlyingSBClass);507 llvm::StringRef SBClassNameRef(SBClassName);508 SBClassNameRef.consume_front("lldb::");509 510 std::string ServerReferenceLine;511 llvm::raw_string_ostream ServerReferenceLineStream(ServerReferenceLine);512 ServerReferenceLineStream << "rpc_server::" << SBClassNameRef513 << "ServerReference " << CallbackParam.Name514 << "_ref(";515 516 if (UnderlyingSBClass.isConstQualified()) {517 QualType NonConstSBType =518 method.Context.getLValueReferenceType(UnqualifiedUnderlyingSBClass);519 ServerReferenceLineStream << "const_cast<" << NonConstSBType << ">(";520 }521 ServerReferenceLineStream << CallbackParam.Name;522 if (UnderlyingSBClass.isConstQualified())523 ServerReferenceLineStream << ")";524 525 ServerReferenceLineStream << ");";526 EmitLine(ServerReferenceLine);527 EmitLine(528 CallbackParam.Name +529 "_ref.Encode(send, rpc_common::RPCPacket::ValueType::Argument);");530 } else {531 std::string ParamName;532 if (CallbackParam.Type->isVoidPointerType())533 ParamName = "callback_info->baton";534 else535 ParamName = CallbackParam.Name;536 EmitLine(537 "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " +538 ParamName + ");");539 }540 }541 542 if (!FuncProtoType->getReturnType()->isVoidType()) {543 EmitLine("// Storage for return value");544 const bool ReturnsSBClass =545 lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType());546 std::string ReturnValueLine = CallbackReturnTypeName;547 llvm::raw_string_ostream ReturnValueLineStream(ReturnValueLine);548 549 if (ReturnsSBClass)550 ReturnValueLineStream << " *";551 ReturnValueLineStream << " __result = ";552 if (ReturnsSBClass)553 ReturnValueLineStream << "nullptr";554 else555 ReturnValueLineStream << "{}";556 ReturnValueLineStream << ";";557 EmitLine(ReturnValueLine);558 }559 560 EmitLine(561 "if (connection_sp->SendRPCCallAndWaitForResponse(send, response)) {");562 IndentLevel++;563 if (!FuncProtoType->getReturnType()->isVoidType()) {564 if (lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType())) {565 EmitLine("__result = rpc_server::RPCServerObjectDecoder<" +566 CallbackReturnTypeName +567 ">(response, rpc_common::RPCPacket::ValueType::ReturnValue);");568 } else569 EmitLine("RPCValueDecoder(response, "570 "rpc_common::RPCPacket::ValueType::ReturnValue, __result);");571 }572 IndentLevel--;573 EmitLine("}");574 if (!FuncProtoType->getReturnType()->isVoidType()) {575 if (lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType()))576 EmitLine("return *__result;");577 else578 EmitLine("return __result;");579 }580 581 // Emit Function Footer;582 IndentLevel--;583 EmitLine("};");584}585