| //===-- RPCCommon.cpp -----------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "RPCCommon.h" |
| |
| #include "clang/AST/AST.h" |
| #include "clang/AST/Attr.h" |
| #include "clang/AST/DeclBase.h" |
| #include "clang/AST/Mangle.h" |
| #include "clang/Lex/Lexer.h" |
| |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include <cstring> |
| |
| using namespace clang; |
| |
| // We intentionally do not generate some classes because they are currently |
| // inconvenient, they aren't really used by most consumers, or we're not sure |
| // why they exist. |
| static constexpr llvm::StringRef DisallowedClasses[] = { |
| "SBCommunication", // This class is pretty much unused by consumers, so we |
| // skip it. |
| "SBInputReader", // This class is pretty much unused by consumers, so we |
| // skip it. |
| "SBCommandPluginInterface", // This class uses virtual functions, and the SB |
| // API should not have those, so we skip this |
| // class. |
| "SBCommand", // There's nothing too difficult about this one, but many of |
| // its methods take a SBCommandPluginInterface pointer so |
| // there's no reason to support this. |
| }; |
| |
| // NOTE: In lldb-rpc-gen, we use mangled names when we need to work with |
| // functions. We do this because we support many functions that have overloads, |
| // and mangled names have no ambiguity which makes it easier to keep track of. |
| // This is also possible since the LLDB SB API is stable. |
| |
| // We intentionally avoid generating certain methods either because they are |
| // difficult to support correctly or they aren't really used much from C++. |
| // NOTE: These methods are marked as deprecated using LLDB_DEPRECATED. |
| // Normally this macro defines to the deprecated annotation, but this |
| // functionality is removed in SBDefines.h when generating SWIG bindings which |
| // we use for testing. Because of this, there is no annotation for the tool to |
| // pick up on so this list will be used while we have this restriction in |
| // SBDefines.h. |
| static constexpr llvm::StringRef DisallowedMethods[] = { |
| // The threading functionality in SBHostOS is deprecated and thus we do not |
| // generate them. It would be ideal to add the annotations to the methods |
| // and then support not generating deprecated methods. However, without |
| // annotations the generator generates most things correctly. This one is |
| // problematic because it returns a pointer to an "opaque" structure |
| // (thread_t) that is not `void *`, so special casing it is more effort than |
| // it's worth. |
| "_ZN4lldb8SBHostOS10ThreadJoinEP17_opaque_pthread_tPPvPNS_7SBErrorE", |
| "_ZN4lldb8SBHostOS12ThreadCancelEP17_opaque_pthread_tPNS_7SBErrorE", |
| "_ZN4lldb8SBHostOS12ThreadCreateEPKcPFPvS3_ES3_PNS_7SBErrorE", |
| "_ZN4lldb8SBHostOS12ThreadDetachEP17_opaque_pthread_tPNS_7SBErrorE", |
| "_ZN4lldb8SBHostOS13ThreadCreatedEPKc", |
| }; |
| |
| static constexpr llvm::StringRef ClassesWithoutDefaultCtor[] = { |
| "SBHostOS", |
| "SBReproducer", |
| }; |
| |
| static constexpr llvm::StringRef ClassesWithoutCopyOperations[] = { |
| "SBHostOS", |
| "SBReproducer", |
| "SBStream", |
| "SBProgress", |
| }; |
| |
| static constexpr llvm::StringRef MethodsWithPointerPlusLen[] = { |
| "_ZN4lldb6SBData11ReadRawDataERNS_7SBErrorEyPvm", |
| "_ZN4lldb6SBData7SetDataERNS_7SBErrorEPKvmNS_9ByteOrderEh", |
| "_ZN4lldb6SBData20SetDataWithOwnershipERNS_7SBErrorEPKvmNS_9ByteOrderEh", |
| "_ZN4lldb6SBData25CreateDataFromUInt64ArrayENS_9ByteOrderEjPym", |
| "_ZN4lldb6SBData25CreateDataFromUInt32ArrayENS_9ByteOrderEjPjm", |
| "_ZN4lldb6SBData25CreateDataFromSInt64ArrayENS_9ByteOrderEjPxm", |
| "_ZN4lldb6SBData25CreateDataFromSInt32ArrayENS_9ByteOrderEjPim", |
| "_ZN4lldb6SBData25CreateDataFromDoubleArrayENS_9ByteOrderEjPdm", |
| "_ZN4lldb6SBData22SetDataFromUInt64ArrayEPym", |
| "_ZN4lldb6SBData22SetDataFromUInt32ArrayEPjm", |
| "_ZN4lldb6SBData22SetDataFromSInt64ArrayEPxm", |
| "_ZN4lldb6SBData22SetDataFromSInt32ArrayEPim", |
| "_ZN4lldb6SBData22SetDataFromDoubleArrayEPdm", |
| "_ZN4lldb10SBDebugger22GetDefaultArchitectureEPcm", |
| "_ZN4lldb10SBDebugger13DispatchInputEPvPKvm", |
| "_ZN4lldb10SBDebugger13DispatchInputEPKvm", |
| "_ZN4lldb6SBFile4ReadEPhmPm", |
| "_ZN4lldb6SBFile5WriteEPKhmPm", |
| "_ZNK4lldb10SBFileSpec7GetPathEPcm", |
| "_ZN4lldb10SBFileSpec11ResolvePathEPKcPcm", |
| "_ZN4lldb8SBModule10GetVersionEPjj", |
| "_ZN4lldb12SBModuleSpec12SetUUIDBytesEPKhm", |
| "_ZNK4lldb9SBProcess9GetSTDOUTEPcm", |
| "_ZNK4lldb9SBProcess9GetSTDERREPcm", |
| "_ZNK4lldb9SBProcess19GetAsyncProfileDataEPcm", |
| "_ZN4lldb9SBProcess10ReadMemoryEyPvmRNS_7SBErrorE", |
| "_ZN4lldb9SBProcess11WriteMemoryEyPKvmRNS_7SBErrorE", |
| "_ZN4lldb9SBProcess21ReadCStringFromMemoryEyPvmRNS_7SBErrorE", |
| "_ZNK4lldb16SBStructuredData14GetStringValueEPcm", |
| "_ZN4lldb8SBTarget23BreakpointCreateByNamesEPPKcjjRKNS_" |
| "14SBFileSpecListES6_", |
| "_ZN4lldb8SBTarget10ReadMemoryENS_9SBAddressEPvmRNS_7SBErrorE", |
| "_ZN4lldb8SBTarget15GetInstructionsENS_9SBAddressEPKvm", |
| "_ZN4lldb8SBTarget25GetInstructionsWithFlavorENS_9SBAddressEPKcPKvm", |
| "_ZN4lldb8SBTarget15GetInstructionsEyPKvm", |
| "_ZN4lldb8SBTarget25GetInstructionsWithFlavorEyPKcPKvm", |
| "_ZN4lldb8SBThread18GetStopDescriptionEPcm", |
| // The below mangled names are used for dummy methods in shell tests |
| // that test the emitters' output. If you're adding any new mangled names |
| // from the actual SB API to this list please add them above. |
| "_ZN4lldb33SBRPC_" |
| "CHECKCONSTCHARPTRPTRWITHLEN27CheckConstCharPtrPtrWithLenEPPKcm", |
| "_ZN4lldb19SBRPC_CHECKARRAYPTR13CheckArrayPtrEPPKcm", |
| "_ZN4lldb18SBRPC_CHECKVOIDPTR12CheckVoidPtrEPvm", |
| }; |
| |
| // These classes inherit from rpc::ObjectRef directly (as opposed to |
| // rpc::LocalObjectRef). Changing them from ObjectRef to LocalObjectRef is ABI |
| // breaking, so we preserve that compatibility here. |
| // |
| // lldb-rpc-gen emits classes as LocalObjectRefs by default. |
| // |
| // FIXME: Does it matter which one it emits by default? |
| static constexpr llvm::StringRef ClassesThatInheritFromObjectRef[] = { |
| "SBAddress", |
| "SBBreakpointName", |
| "SBCommandInterpreter", |
| "SBCommandReturnObject", |
| "SBError", |
| "SBExecutionContext", |
| "SBExpressionOptions", |
| "SBFileSpec", |
| "SBFileSpecList", |
| "SBFormat", |
| "SBFunction", |
| "SBHistoricalFrame", |
| "SBHistoricalLineEntry", |
| "SBHistoricalLineEntryList", |
| "SBLineEntry", |
| "SBStream", |
| "SBStringList", |
| "SBStructuredData", |
| "SBSymbolContext", |
| "SBSymbolContextList", |
| "SBTypeMember", |
| "SBTypeSummaryOptions", |
| "SBValueList", |
| }; |
| |
| QualType lldb_rpc_gen::GetUnderlyingType(QualType T) { |
| QualType UnderlyingType; |
| if (T->isPointerType()) |
| UnderlyingType = T->getPointeeType(); |
| else if (T->isReferenceType()) |
| UnderlyingType = T.getNonReferenceType(); |
| else |
| UnderlyingType = T; |
| |
| return UnderlyingType; |
| } |
| |
| QualType lldb_rpc_gen::GetUnqualifiedUnderlyingType(QualType T) { |
| return GetUnderlyingType(T).getUnqualifiedType(); |
| } |
| |
| std::string lldb_rpc_gen::GetMangledName(ASTContext &Context, |
| CXXMethodDecl *MDecl) { |
| std::string Mangled; |
| llvm::raw_string_ostream MangledStream(Mangled); |
| |
| GlobalDecl GDecl; |
| if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl)) |
| GDecl = GlobalDecl(CtorDecl, Ctor_Complete); |
| else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(MDecl)) |
| GDecl = GlobalDecl(DtorDecl, Dtor_Deleting); |
| else |
| GDecl = GlobalDecl(MDecl); |
| |
| MangleContext *MC = Context.createMangleContext(); |
| MC->mangleName(GDecl, MangledStream); |
| return Mangled; |
| } |
| |
| static auto CheckTypeForLLDBPrivate = [](const Type *Ty) {}; |
| bool lldb_rpc_gen::TypeIsFromLLDBPrivate(QualType T) { |
| auto CheckTypeForLLDBPrivate = [](const Type *Ty) { |
| if (!Ty) |
| return false; |
| const auto *CXXRDecl = Ty->getAsCXXRecordDecl(); |
| if (!CXXRDecl) |
| return false; |
| const auto *NSDecl = |
| llvm::dyn_cast<NamespaceDecl>(CXXRDecl->getDeclContext()); |
| if (!NSDecl) |
| return false; |
| return NSDecl->getName() == "lldb_private"; |
| }; |
| |
| // First, get the underlying type (remove qualifications and strip off any |
| // pointers/references). Then we'll need to desugar this type. This will |
| // remove things like typedefs, so instead of seeing "lldb::DebuggerSP" we'll |
| // actually see something like "std::shared_ptr<lldb_private::Debugger>". |
| QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T); |
| const Type *DesugaredType = |
| UnqualifiedUnderlyingType->getUnqualifiedDesugaredType(); |
| assert(DesugaredType && "DesugaredType from a valid Type is nullptr!"); |
| |
| // Check the type itself. |
| if (CheckTypeForLLDBPrivate(DesugaredType)) |
| return true; |
| |
| // If that didn't work, it's possible that the type has a template argument |
| // that is an lldb_private type. |
| if (const auto *TemplateSDecl = |
| llvm::dyn_cast_or_null<ClassTemplateSpecializationDecl>( |
| DesugaredType->getAsCXXRecordDecl())) { |
| for (const TemplateArgument &TA : |
| TemplateSDecl->getTemplateArgs().asArray()) { |
| if (TA.getKind() != TemplateArgument::Type) |
| continue; |
| if (CheckTypeForLLDBPrivate(TA.getAsType().getTypePtr())) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool lldb_rpc_gen::TypeIsSBClass(QualType T) { |
| QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T); |
| const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl(); |
| if (!CXXRDecl) |
| return false; // SB Classes are always C++ classes |
| |
| return CXXRDecl->getName().starts_with("SB"); |
| } |
| |
| bool lldb_rpc_gen::TypeIsConstCharPtr(QualType T) { |
| if (!T->isPointerType()) |
| return false; |
| |
| QualType UnderlyingType = T->getPointeeType(); |
| if (!UnderlyingType.isConstQualified()) |
| return false; |
| |
| // NOTE: We should be able to do `UnderlyingType->isCharType` but that will |
| // return true for `const uint8_t *` since that is effectively an unsigned |
| // char pointer. We currently do not support pointers other than `const char |
| // *` and `const char **`. |
| |
| // NOTE: Checking that the underlying type is a signed integer works on Darwin |
| // platforms, but Linux platforms expect that the underlying type is an |
| // unsigned integer. |
| return UnderlyingType->isSpecificBuiltinType(BuiltinType::Char_S) || |
| UnderlyingType->isSpecificBuiltinType(BuiltinType::SChar) || |
| UnderlyingType->isSpecificBuiltinType(BuiltinType::Char_U) || |
| UnderlyingType->isSpecificBuiltinType(BuiltinType::UChar); |
| } |
| |
| bool lldb_rpc_gen::TypeIsConstCharPtrPtr(QualType T) { |
| if (!T->isPointerType()) |
| return false; |
| |
| return TypeIsConstCharPtr(T->getPointeeType()); |
| } |
| |
| bool lldb_rpc_gen::TypeIsDisallowedClass(QualType T) { |
| QualType UUT = GetUnqualifiedUnderlyingType(T); |
| const auto *CXXRDecl = UUT->getAsCXXRecordDecl(); |
| if (!CXXRDecl) |
| return false; |
| |
| llvm::StringRef DeclName = CXXRDecl->getName(); |
| for (const llvm::StringRef DisallowedClass : DisallowedClasses) |
| if (DeclName == DisallowedClass) |
| return true; |
| return false; |
| } |
| |
| bool lldb_rpc_gen::TypeIsCallbackFunctionPointer(QualType T) { |
| return T->isFunctionPointerType(); |
| } |
| |
| bool lldb_rpc_gen::MethodIsDisallowed(ASTContext &Context, |
| CXXMethodDecl *MDecl) { |
| bool isDisallowed = false; |
| std::string MangledName = lldb_rpc_gen::GetMangledName(Context, MDecl); |
| if (llvm::is_contained(DisallowedMethods, MangledName)) |
| isDisallowed = true; |
| |
| if (MDecl->hasAttrs()) { |
| for (auto *attr : MDecl->getAttrs()) { |
| if (strcmp(attr->getAttrName()->getNameStart(), "deprecated") == 0) |
| isDisallowed = true; |
| } |
| } |
| return isDisallowed; |
| } |
| |
| bool lldb_rpc_gen::HasCallbackParameter(CXXMethodDecl *MDecl) { |
| bool HasCallbackParameter = false; |
| bool HasBatonParameter = false; |
| auto End = MDecl->parameters().end(); |
| for (auto Iter = MDecl->parameters().begin(); Iter != End; Iter++) { |
| if ((*Iter)->getType()->isFunctionPointerType()) |
| HasCallbackParameter = true; |
| else if ((*Iter)->getType()->isVoidPointerType()) |
| HasBatonParameter = true; |
| } |
| |
| return HasCallbackParameter && HasBatonParameter; |
| } |
| |
| // NOTE: There's possibly a more clever way to do this, but we're keeping |
| // the string replacement way here. Here is why it is written this way: |
| // By the time we have already created a `Method` object, we have extracted the |
| // `QualifiedName` and the relevant QualTypes for parameters/return types, many |
| // of which contains "lldb::" in them. To change it in a way that would be |
| // friendly to liblldbrpc, we would need to have a way of replacing that |
| // namespace at the time of creating a Method, and only for liblldbrpc methods. |
| // IMO this would complicate Method more than what I'm doing here, and not |
| // necessarily for any more benefit. |
| // In clang-tools-extra, there is a ChangeNamespaces tool which tries to do |
| // something similar to this. It also operates primarily on string replacement, |
| // but uses more sophisticated clang tooling to do so. |
| // For now, this will do what we need it to do. |
| std::string |
| lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(std::string Name) { |
| const char *lldb_namespace = "lldb::"; |
| auto Pos = Name.find(lldb_namespace); |
| while (Pos != std::string::npos) { |
| constexpr size_t SizeOfLLDBNamespace = 6; |
| Name.replace(Pos, SizeOfLLDBNamespace, "lldb_rpc::"); |
| Pos = Name.find(lldb_namespace); |
| } |
| return Name; |
| } |
| |
| std::string lldb_rpc_gen::StripLLDBNamespace(std::string Name) { |
| const char *lldb_namespace = "lldb::"; |
| auto Pos = Name.find(lldb_namespace); |
| if (Pos != std::string::npos) { |
| constexpr size_t SizeOfLLDBNamespace = 6; |
| Name = Name.substr(Pos + SizeOfLLDBNamespace); |
| } |
| return Name; |
| } |
| |
| bool lldb_rpc_gen::SBClassRequiresDefaultCtor(const std::string &ClassName) { |
| return !llvm::is_contained(ClassesWithoutDefaultCtor, ClassName); |
| } |
| |
| bool lldb_rpc_gen::SBClassRequiresCopyCtorAssign(const std::string &ClassName) { |
| return !llvm::is_contained(ClassesWithoutCopyOperations, ClassName); |
| } |
| |
| bool lldb_rpc_gen::SBClassInheritsFromObjectRef(const std::string &ClassName) { |
| return llvm::is_contained(ClassesThatInheritFromObjectRef, ClassName); |
| } |
| |
| std::string lldb_rpc_gen::GetSBClassNameFromType(QualType T) { |
| assert(lldb_rpc_gen::TypeIsSBClass(T) && |
| "Cannot get SBClass name from non-SB class type!"); |
| |
| QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T); |
| const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl(); |
| assert(CXXRDecl && "SB class was not CXXRecordDecl!"); |
| if (!CXXRDecl) |
| return std::string(); |
| |
| return CXXRDecl->getName().str(); |
| } |
| lldb_rpc_gen::Method::Method(CXXMethodDecl *MDecl, const PrintingPolicy &Policy, |
| ASTContext &Context) |
| : Policy(Policy), Context(Context), |
| QualifiedName(MDecl->getQualifiedNameAsString()), |
| BaseName(MDecl->getNameAsString()), |
| MangledName(lldb_rpc_gen::GetMangledName(Context, MDecl)), |
| ReturnType(MDecl->getReturnType()), IsConst(MDecl->isConst()), |
| IsInstance(MDecl->isInstance()), IsCtor(isa<CXXConstructorDecl>(MDecl)), |
| IsCopyAssign(MDecl->isCopyAssignmentOperator()), |
| IsMoveAssign(MDecl->isMoveAssignmentOperator()), |
| IsDtor(isa<CXXDestructorDecl>(MDecl)), |
| IsConversionMethod(isa<CXXConversionDecl>(MDecl)) { |
| uint8_t UnnamedArgIdx = 0; |
| bool PrevParamWasPointer = false; |
| for (const auto *ParamDecl : MDecl->parameters()) { |
| Param param; |
| if (ParamDecl->hasDefaultArg()) |
| param.DefaultValueText = |
| Lexer::getSourceText( |
| CharSourceRange::getTokenRange( |
| ParamDecl->getDefaultArg()->getSourceRange()), |
| Context.getSourceManager(), Context.getLangOpts()) |
| .str(); |
| |
| param.IsFollowedByLen = false; |
| param.Name = ParamDecl->getNameAsString(); |
| // If the parameter has no name, we'll generate one |
| if (param.Name.empty()) { |
| param.Name = "arg" + std::to_string(UnnamedArgIdx); |
| UnnamedArgIdx++; |
| } |
| param.Type = ParamDecl->getType(); |
| |
| // FIXME: Instead of using this heuristic, the ideal thing would be to add |
| // annotations to the SBAPI methods themselves. For now, we have a list of |
| // methods that we know will need this. |
| if (PrevParamWasPointer) { |
| PrevParamWasPointer = false; |
| const bool IsIntegerType = param.Type->isIntegerType() && |
| !param.Type->isBooleanType() && |
| !param.Type->isEnumeralType(); |
| if (IsIntegerType && llvm::is_contained(MethodsWithPointerPlusLen, |
| llvm::StringRef(MangledName))) |
| Params.back().IsFollowedByLen = true; |
| } |
| |
| if (param.Type->isPointerType() && |
| !lldb_rpc_gen::TypeIsConstCharPtr(param.Type) && |
| !param.Type->isFunctionPointerType()) |
| PrevParamWasPointer = true; |
| |
| if (param.Type->isFunctionPointerType()) |
| ContainsFunctionPointerParameter = true; |
| |
| Params.push_back(param); |
| } |
| |
| if (IsInstance) |
| ThisType = MDecl->getThisType(); |
| |
| if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl)) { |
| IsExplicitCtorOrConversionMethod = CtorDecl->isExplicit(); |
| IsCopyCtor = CtorDecl->isCopyConstructor(); |
| IsMoveCtor = CtorDecl->isMoveConstructor(); |
| } else if (const auto *ConversionDecl = dyn_cast<CXXConversionDecl>(MDecl)) |
| IsExplicitCtorOrConversionMethod = ConversionDecl->isExplicit(); |
| } |
| |
| // Adding a '<' allows us to use Methods in ordered containers. |
| // The ordering is on memory addresses. |
| bool lldb_rpc_gen::Method::operator<(const lldb_rpc_gen::Method &rhs) const { |
| return this < &rhs; |
| } |
| |
| std::string |
| lldb_rpc_gen::Method::CreateParamListAsString(GenerationKind Generation, |
| bool IncludeDefaultValue) const { |
| assert((!IncludeDefaultValue || Generation == eLibrary) && |
| "Default values should only be emitted on the library side!"); |
| |
| std::vector<std::string> ParamList; |
| |
| if (Generation == eLibrary && RequiresConnectionParameter()) |
| ParamList.push_back("const rpc::Connection &connection"); |
| |
| for (const auto &Param : Params) { |
| std::string ParamString; |
| llvm::raw_string_ostream ParamStringStream(ParamString); |
| |
| if (Generation == eLibrary) |
| ParamStringStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( |
| Param.Type.getAsString(Policy)); |
| else |
| ParamStringStream << Param.Type.getAsString(Policy); |
| |
| ParamStringStream << " " << Param.Name; |
| if (IncludeDefaultValue && Generation == eLibrary && |
| !Param.DefaultValueText.empty()) |
| ParamStringStream << " = " |
| << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( |
| Param.DefaultValueText); |
| |
| ParamList.push_back(ParamString); |
| } |
| |
| return llvm::join(ParamList, ", "); |
| } |
| |
| bool lldb_rpc_gen::Method::RequiresConnectionParameter() const { |
| if (!IsCtor && IsInstance) |
| return false; |
| if (IsCopyCtor || IsMoveCtor) |
| return false; |
| for (const auto &Param : Params) { |
| // We can re-use the connection from our parameter if possible. |
| // Const-qualified parameters are input parameters and already |
| // have a valid connection to provide to the current method. |
| if (TypeIsSBClass(Param.Type) && |
| GetUnderlyingType(Param.Type).isConstQualified()) |
| return false; |
| } |
| |
| return true; |
| } |