494 lines · cpp
1//===-- RPCCommon.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 "RPCCommon.h"10 11#include "clang/AST/AST.h"12#include "clang/AST/Attr.h"13#include "clang/AST/DeclBase.h"14#include "clang/AST/Mangle.h"15#include "clang/Lex/Lexer.h"16 17#include "llvm/ADT/STLExtras.h"18#include "llvm/ADT/StringExtras.h"19#include "llvm/ADT/StringMap.h"20#include "llvm/ADT/StringRef.h"21#include "llvm/Support/raw_ostream.h"22 23#include <cstring>24 25using namespace clang;26 27// We intentionally do not generate some classes because they are currently28// inconvenient, they aren't really used by most consumers, or we're not sure29// why they exist.30static constexpr llvm::StringRef DisallowedClasses[] = {31 "SBCommunication", // This class is pretty much unused by consumers, so we32 // skip it.33 "SBInputReader", // This class is pretty much unused by consumers, so we34 // skip it.35 "SBCommandPluginInterface", // This class uses virtual functions, and the SB36 // API should not have those, so we skip this37 // class.38 "SBCommand", // There's nothing too difficult about this one, but many of39 // its methods take a SBCommandPluginInterface pointer so40 // there's no reason to support this.41};42 43// NOTE: In lldb-rpc-gen, we use mangled names when we need to work with44// functions. We do this because we support many functions that have overloads,45// and mangled names have no ambiguity which makes it easier to keep track of.46// This is also possible since the LLDB SB API is stable.47 48// We intentionally avoid generating certain methods either because they are49// difficult to support correctly or they aren't really used much from C++.50// NOTE: These methods are marked as deprecated using LLDB_DEPRECATED.51// Normally this macro defines to the deprecated annotation, but this52// functionality is removed in SBDefines.h when generating SWIG bindings which53// we use for testing. Because of this, there is no annotation for the tool to54// pick up on so this list will be used while we have this restriction in55// SBDefines.h.56static constexpr llvm::StringRef DisallowedMethods[] = {57 // The threading functionality in SBHostOS is deprecated and thus we do not58 // generate them. It would be ideal to add the annotations to the methods59 // and then support not generating deprecated methods. However, without60 // annotations the generator generates most things correctly. This one is61 // problematic because it returns a pointer to an "opaque" structure62 // (thread_t) that is not `void *`, so special casing it is more effort than63 // it's worth.64 "_ZN4lldb8SBHostOS10ThreadJoinEP17_opaque_pthread_tPPvPNS_7SBErrorE",65 "_ZN4lldb8SBHostOS12ThreadCancelEP17_opaque_pthread_tPNS_7SBErrorE",66 "_ZN4lldb8SBHostOS12ThreadCreateEPKcPFPvS3_ES3_PNS_7SBErrorE",67 "_ZN4lldb8SBHostOS12ThreadDetachEP17_opaque_pthread_tPNS_7SBErrorE",68 "_ZN4lldb8SBHostOS13ThreadCreatedEPKc",69};70 71static constexpr llvm::StringRef ClassesWithoutDefaultCtor[] = {72 "SBHostOS",73 "SBReproducer",74};75 76static constexpr llvm::StringRef ClassesWithoutCopyOperations[] = {77 "SBHostOS",78 "SBReproducer",79 "SBStream",80 "SBProgress",81};82 83static constexpr llvm::StringRef MethodsWithPointerPlusLen[] = {84 "_ZN4lldb6SBData11ReadRawDataERNS_7SBErrorEyPvm",85 "_ZN4lldb6SBData7SetDataERNS_7SBErrorEPKvmNS_9ByteOrderEh",86 "_ZN4lldb6SBData20SetDataWithOwnershipERNS_7SBErrorEPKvmNS_9ByteOrderEh",87 "_ZN4lldb6SBData25CreateDataFromUInt64ArrayENS_9ByteOrderEjPym",88 "_ZN4lldb6SBData25CreateDataFromUInt32ArrayENS_9ByteOrderEjPjm",89 "_ZN4lldb6SBData25CreateDataFromSInt64ArrayENS_9ByteOrderEjPxm",90 "_ZN4lldb6SBData25CreateDataFromSInt32ArrayENS_9ByteOrderEjPim",91 "_ZN4lldb6SBData25CreateDataFromDoubleArrayENS_9ByteOrderEjPdm",92 "_ZN4lldb6SBData22SetDataFromUInt64ArrayEPym",93 "_ZN4lldb6SBData22SetDataFromUInt32ArrayEPjm",94 "_ZN4lldb6SBData22SetDataFromSInt64ArrayEPxm",95 "_ZN4lldb6SBData22SetDataFromSInt32ArrayEPim",96 "_ZN4lldb6SBData22SetDataFromDoubleArrayEPdm",97 "_ZN4lldb10SBDebugger22GetDefaultArchitectureEPcm",98 "_ZN4lldb10SBDebugger13DispatchInputEPvPKvm",99 "_ZN4lldb10SBDebugger13DispatchInputEPKvm",100 "_ZN4lldb6SBFile4ReadEPhmPm",101 "_ZN4lldb6SBFile5WriteEPKhmPm",102 "_ZNK4lldb10SBFileSpec7GetPathEPcm",103 "_ZN4lldb10SBFileSpec11ResolvePathEPKcPcm",104 "_ZN4lldb8SBModule10GetVersionEPjj",105 "_ZN4lldb12SBModuleSpec12SetUUIDBytesEPKhm",106 "_ZNK4lldb9SBProcess9GetSTDOUTEPcm",107 "_ZNK4lldb9SBProcess9GetSTDERREPcm",108 "_ZNK4lldb9SBProcess19GetAsyncProfileDataEPcm",109 "_ZN4lldb9SBProcess10ReadMemoryEyPvmRNS_7SBErrorE",110 "_ZN4lldb9SBProcess11WriteMemoryEyPKvmRNS_7SBErrorE",111 "_ZN4lldb9SBProcess21ReadCStringFromMemoryEyPvmRNS_7SBErrorE",112 "_ZNK4lldb16SBStructuredData14GetStringValueEPcm",113 "_ZN4lldb8SBTarget23BreakpointCreateByNamesEPPKcjjRKNS_"114 "14SBFileSpecListES6_",115 "_ZN4lldb8SBTarget10ReadMemoryENS_9SBAddressEPvmRNS_7SBErrorE",116 "_ZN4lldb8SBTarget15GetInstructionsENS_9SBAddressEPKvm",117 "_ZN4lldb8SBTarget25GetInstructionsWithFlavorENS_9SBAddressEPKcPKvm",118 "_ZN4lldb8SBTarget15GetInstructionsEyPKvm",119 "_ZN4lldb8SBTarget25GetInstructionsWithFlavorEyPKcPKvm",120 "_ZN4lldb8SBThread18GetStopDescriptionEPcm",121 // The below mangled names are used for dummy methods in shell tests122 // that test the emitters' output. If you're adding any new mangled names123 // from the actual SB API to this list please add them above.124 "_ZN4lldb33SBRPC_"125 "CHECKCONSTCHARPTRPTRWITHLEN27CheckConstCharPtrPtrWithLenEPPKcm",126 "_ZN4lldb19SBRPC_CHECKARRAYPTR13CheckArrayPtrEPPKcm",127 "_ZN4lldb18SBRPC_CHECKVOIDPTR12CheckVoidPtrEPvm",128};129 130// These classes inherit from rpc::ObjectRef directly (as opposed to131// rpc::LocalObjectRef). Changing them from ObjectRef to LocalObjectRef is ABI132// breaking, so we preserve that compatibility here.133//134// lldb-rpc-gen emits classes as LocalObjectRefs by default.135//136// FIXME: Does it matter which one it emits by default?137static constexpr llvm::StringRef ClassesThatInheritFromObjectRef[] = {138 "SBAddress",139 "SBBreakpointName",140 "SBCommandInterpreter",141 "SBCommandReturnObject",142 "SBError",143 "SBExecutionContext",144 "SBExpressionOptions",145 "SBFileSpec",146 "SBFileSpecList",147 "SBFormat",148 "SBFunction",149 "SBHistoricalFrame",150 "SBHistoricalLineEntry",151 "SBHistoricalLineEntryList",152 "SBLineEntry",153 "SBStream",154 "SBStringList",155 "SBStructuredData",156 "SBSymbolContext",157 "SBSymbolContextList",158 "SBTypeMember",159 "SBTypeSummaryOptions",160 "SBValueList",161};162 163QualType lldb_rpc_gen::GetUnderlyingType(QualType T) {164 QualType UnderlyingType;165 if (T->isPointerType())166 UnderlyingType = T->getPointeeType();167 else if (T->isReferenceType())168 UnderlyingType = T.getNonReferenceType();169 else170 UnderlyingType = T;171 172 return UnderlyingType;173}174 175QualType lldb_rpc_gen::GetUnqualifiedUnderlyingType(QualType T) {176 return GetUnderlyingType(T).getUnqualifiedType();177}178 179std::string lldb_rpc_gen::GetMangledName(ASTContext &Context,180 CXXMethodDecl *MDecl) {181 std::string Mangled;182 llvm::raw_string_ostream MangledStream(Mangled);183 184 GlobalDecl GDecl;185 if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl))186 GDecl = GlobalDecl(CtorDecl, Ctor_Complete);187 else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(MDecl))188 GDecl = GlobalDecl(DtorDecl, Dtor_Deleting);189 else190 GDecl = GlobalDecl(MDecl);191 192 MangleContext *MC = Context.createMangleContext();193 MC->mangleName(GDecl, MangledStream);194 return Mangled;195}196 197bool lldb_rpc_gen::TypeIsFromLLDBPrivate(QualType T) {198 auto CheckTypeForLLDBPrivate = [](const Type *Ty) {199 if (!Ty)200 return false;201 const auto *CXXRDecl = Ty->getAsCXXRecordDecl();202 if (!CXXRDecl)203 return false;204 const auto *NSDecl =205 llvm::dyn_cast<NamespaceDecl>(CXXRDecl->getDeclContext());206 if (!NSDecl)207 return false;208 return NSDecl->getName() == "lldb_private";209 };210 211 // First, get the underlying type (remove qualifications and strip off any212 // pointers/references). Then we'll need to desugar this type. This will213 // remove things like typedefs, so instead of seeing "lldb::DebuggerSP" we'll214 // actually see something like "std::shared_ptr<lldb_private::Debugger>".215 QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T);216 const Type *DesugaredType =217 UnqualifiedUnderlyingType->getUnqualifiedDesugaredType();218 assert(DesugaredType && "DesugaredType from a valid Type is nullptr!");219 220 // Check the type itself.221 if (CheckTypeForLLDBPrivate(DesugaredType))222 return true;223 224 // If that didn't work, it's possible that the type has a template argument225 // that is an lldb_private type.226 if (const auto *TemplateSDecl =227 llvm::dyn_cast_or_null<ClassTemplateSpecializationDecl>(228 DesugaredType->getAsCXXRecordDecl())) {229 for (const TemplateArgument &TA :230 TemplateSDecl->getTemplateArgs().asArray()) {231 if (TA.getKind() != TemplateArgument::Type)232 continue;233 if (CheckTypeForLLDBPrivate(TA.getAsType().getTypePtr()))234 return true;235 }236 }237 return false;238}239 240bool lldb_rpc_gen::TypeIsSBClass(QualType T) {241 QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T);242 const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl();243 if (!CXXRDecl)244 return false; // SB Classes are always C++ classes245 246 return CXXRDecl->getName().starts_with("SB");247}248 249bool lldb_rpc_gen::TypeIsConstCharPtr(QualType T) {250 if (!T->isPointerType())251 return false;252 253 QualType UnderlyingType = T->getPointeeType();254 if (!UnderlyingType.isConstQualified())255 return false;256 257 // NOTE: We should be able to do `UnderlyingType->isCharType` but that will258 // return true for `const uint8_t *` since that is effectively an unsigned259 // char pointer. We currently do not support pointers other than `const char260 // *` and `const char **`.261 262 // NOTE: Checking that the underlying type is a signed integer works on Darwin263 // platforms, but Linux platforms expect that the underlying type is an264 // unsigned integer.265 return UnderlyingType->isSpecificBuiltinType(BuiltinType::Char_S) ||266 UnderlyingType->isSpecificBuiltinType(BuiltinType::SChar) ||267 UnderlyingType->isSpecificBuiltinType(BuiltinType::Char_U) ||268 UnderlyingType->isSpecificBuiltinType(BuiltinType::UChar);269}270 271bool lldb_rpc_gen::TypeIsConstCharPtrPtr(QualType T) {272 if (!T->isPointerType())273 return false;274 275 return TypeIsConstCharPtr(T->getPointeeType());276}277 278bool lldb_rpc_gen::TypeIsDisallowedClass(QualType T) {279 QualType UUT = GetUnqualifiedUnderlyingType(T);280 const auto *CXXRDecl = UUT->getAsCXXRecordDecl();281 if (!CXXRDecl)282 return false;283 284 llvm::StringRef DeclName = CXXRDecl->getName();285 for (const llvm::StringRef DisallowedClass : DisallowedClasses)286 if (DeclName == DisallowedClass)287 return true;288 return false;289}290 291bool lldb_rpc_gen::TypeIsCallbackFunctionPointer(QualType T) {292 return T->isFunctionPointerType();293}294 295bool lldb_rpc_gen::MethodIsDisallowed(ASTContext &Context,296 CXXMethodDecl *MDecl) {297 bool isDisallowed = false;298 std::string MangledName = lldb_rpc_gen::GetMangledName(Context, MDecl);299 if (llvm::is_contained(DisallowedMethods, MangledName))300 isDisallowed = true;301 302 if (MDecl->hasAttrs()) {303 for (auto *attr : MDecl->getAttrs()) {304 if (strcmp(attr->getAttrName()->getNameStart(), "deprecated") == 0)305 isDisallowed = true;306 }307 }308 return isDisallowed;309}310 311// NOTE: There's possibly a more clever way to do this, but we're keeping312// the string replacement way here. Here is why it is written this way:313// By the time we have already created a `Method` object, we have extracted the314// `QualifiedName` and the relevant QualTypes for parameters/return types, many315// of which contains "lldb::" in them. To change it in a way that would be316// friendly to liblldbrpc, we would need to have a way of replacing that317// namespace at the time of creating a Method, and only for liblldbrpc methods.318// IMO this would complicate Method more than what I'm doing here, and not319// necessarily for any more benefit.320// In clang-tools-extra, there is a ChangeNamespaces tool which tries to do321// something similar to this. It also operates primarily on string replacement,322// but uses more sophisticated clang tooling to do so.323// For now, this will do what we need it to do.324std::string325lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(std::string Name) {326 const char *lldb_namespace = "lldb::";327 auto Pos = Name.find(lldb_namespace);328 while (Pos != std::string::npos) {329 constexpr size_t SizeOfLLDBNamespace = 6;330 Name.replace(Pos, SizeOfLLDBNamespace, "lldb_rpc::");331 Pos = Name.find(lldb_namespace);332 }333 return Name;334}335 336std::string lldb_rpc_gen::StripLLDBNamespace(std::string Name) {337 const char *lldb_namespace = "lldb::";338 auto Pos = Name.find(lldb_namespace);339 if (Pos != std::string::npos) {340 constexpr size_t SizeOfLLDBNamespace = 6;341 Name = Name.substr(Pos + SizeOfLLDBNamespace);342 }343 return Name;344}345 346bool lldb_rpc_gen::SBClassRequiresDefaultCtor(const std::string &ClassName) {347 return !llvm::is_contained(ClassesWithoutDefaultCtor, ClassName);348}349 350bool lldb_rpc_gen::SBClassRequiresCopyCtorAssign(const std::string &ClassName) {351 return !llvm::is_contained(ClassesWithoutCopyOperations, ClassName);352}353 354bool lldb_rpc_gen::SBClassInheritsFromObjectRef(const std::string &ClassName) {355 return llvm::is_contained(ClassesThatInheritFromObjectRef, ClassName);356}357 358std::string lldb_rpc_gen::GetSBClassNameFromType(QualType T) {359 assert(lldb_rpc_gen::TypeIsSBClass(T) &&360 "Cannot get SBClass name from non-SB class type!");361 362 QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T);363 const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl();364 assert(CXXRDecl && "SB class was not CXXRecordDecl!");365 if (!CXXRDecl)366 return std::string();367 368 return CXXRDecl->getName().str();369}370lldb_rpc_gen::Method::Method(CXXMethodDecl *MDecl, const PrintingPolicy &Policy,371 ASTContext &Context)372 : Policy(Policy), Context(Context),373 QualifiedName(MDecl->getQualifiedNameAsString()),374 BaseName(MDecl->getNameAsString()),375 MangledName(lldb_rpc_gen::GetMangledName(Context, MDecl)),376 ReturnType(MDecl->getReturnType()), IsConst(MDecl->isConst()),377 IsInstance(MDecl->isInstance()), IsCtor(isa<CXXConstructorDecl>(MDecl)),378 IsCopyAssign(MDecl->isCopyAssignmentOperator()),379 IsMoveAssign(MDecl->isMoveAssignmentOperator()),380 IsDtor(isa<CXXDestructorDecl>(MDecl)),381 IsConversionMethod(isa<CXXConversionDecl>(MDecl)) {382 uint8_t UnnamedArgIdx = 0;383 bool PrevParamWasPointer = false;384 for (const auto *ParamDecl : MDecl->parameters()) {385 Param param;386 if (ParamDecl->hasDefaultArg())387 param.DefaultValueText =388 Lexer::getSourceText(389 CharSourceRange::getTokenRange(390 ParamDecl->getDefaultArg()->getSourceRange()),391 Context.getSourceManager(), Context.getLangOpts())392 .str();393 394 param.IsFollowedByLen = false;395 param.Name = ParamDecl->getNameAsString();396 // If the parameter has no name, we'll generate one397 if (param.Name.empty()) {398 param.Name = "arg" + std::to_string(UnnamedArgIdx);399 UnnamedArgIdx++;400 }401 param.Type = ParamDecl->getType();402 403 // FIXME: Instead of using this heuristic, the ideal thing would be to add404 // annotations to the SBAPI methods themselves. For now, we have a list of405 // methods that we know will need this.406 if (PrevParamWasPointer) {407 PrevParamWasPointer = false;408 const bool IsIntegerType = param.Type->isIntegerType() &&409 !param.Type->isBooleanType() &&410 !param.Type->isEnumeralType();411 if (IsIntegerType && llvm::is_contained(MethodsWithPointerPlusLen,412 llvm::StringRef(MangledName)))413 Params.back().IsFollowedByLen = true;414 }415 416 if (param.Type->isPointerType() &&417 !lldb_rpc_gen::TypeIsConstCharPtr(param.Type) &&418 !param.Type->isFunctionPointerType())419 PrevParamWasPointer = true;420 421 if (param.Type->isFunctionPointerType())422 ContainsFunctionPointerParameter = true;423 424 Params.push_back(param);425 }426 427 if (IsInstance)428 ThisType = MDecl->getThisType();429 430 if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl)) {431 IsExplicitCtorOrConversionMethod = CtorDecl->isExplicit();432 IsCopyCtor = CtorDecl->isCopyConstructor();433 IsMoveCtor = CtorDecl->isMoveConstructor();434 } else if (const auto *ConversionDecl = dyn_cast<CXXConversionDecl>(MDecl))435 IsExplicitCtorOrConversionMethod = ConversionDecl->isExplicit();436}437 438// Adding a '<' allows us to use Methods in ordered containers.439// The ordering is on memory addresses.440bool lldb_rpc_gen::Method::operator<(const lldb_rpc_gen::Method &rhs) const {441 return this < &rhs;442}443 444std::string445lldb_rpc_gen::Method::CreateParamListAsString(GenerationKind Generation,446 bool IncludeDefaultValue) const {447 assert((!IncludeDefaultValue || Generation == eLibrary) &&448 "Default values should only be emitted on the library side!");449 450 std::vector<std::string> ParamList;451 452 if (Generation == eLibrary && RequiresConnectionParameter())453 ParamList.push_back("const rpc::Connection &connection");454 455 for (const auto &Param : Params) {456 std::string ParamString;457 llvm::raw_string_ostream ParamStringStream(ParamString);458 459 if (Generation == eLibrary)460 ParamStringStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(461 Param.Type.getAsString(Policy));462 else463 ParamStringStream << Param.Type.getAsString(Policy);464 465 ParamStringStream << " " << Param.Name;466 if (IncludeDefaultValue && Generation == eLibrary &&467 !Param.DefaultValueText.empty())468 ParamStringStream << " = "469 << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(470 Param.DefaultValueText);471 472 ParamList.push_back(ParamString);473 }474 475 return llvm::join(ParamList, ", ");476}477 478bool lldb_rpc_gen::Method::RequiresConnectionParameter() const {479 if (!IsCtor && IsInstance)480 return false;481 if (IsCopyCtor || IsMoveCtor)482 return false;483 for (const auto &Param : Params) {484 // We can re-use the connection from our parameter if possible.485 // Const-qualified parameters are input parameters and already486 // have a valid connection to provide to the current method.487 if (TypeIsSBClass(Param.Type) &&488 GetUnderlyingType(Param.Type).isConstQualified())489 return false;490 }491 492 return true;493}494