//===-- 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 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(MDecl)) GDecl = GlobalDecl(CtorDecl, Ctor_Complete); else if (const auto *DtorDecl = dyn_cast(MDecl)) GDecl = GlobalDecl(DtorDecl, Dtor_Deleting); else GDecl = GlobalDecl(MDecl); MangleContext *MC = Context.createMangleContext(); MC->mangleName(GDecl, MangledStream); return Mangled; } 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(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". 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( 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; } // 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(MDecl)), IsCopyAssign(MDecl->isCopyAssignmentOperator()), IsMoveAssign(MDecl->isMoveAssignmentOperator()), IsDtor(isa(MDecl)), IsConversionMethod(isa(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(MDecl)) { IsExplicitCtorOrConversionMethod = CtorDecl->isExplicit(); IsCopyCtor = CtorDecl->isCopyConstructor(); IsMoveCtor = CtorDecl->isMoveConstructor(); } else if (const auto *ConversionDecl = dyn_cast(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 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; }