Index: lldb/lit/SymbolFile/NativePDB/Inputs/function-types-builtins.lldbinit =================================================================== --- /dev/null +++ lldb/lit/SymbolFile/NativePDB/Inputs/function-types-builtins.lldbinit @@ -0,0 +1,70 @@ +command alias dv target variable + +dv aa +dv ab +dv ac +dv ad +dv ae +dv af +dv ag +dv ah +dv ai +dv aj +dv ak +dv al +dv am +dv an +dv ao +dv aq +dv ar +dv as +dv at +dv au +dv av +dv aw +dv ax +dv ay +dv az +dv aaa +dv aab +dv aac +dv aad +dv ra +dv rb +dv rc +dv rd +dv re +dv rf +dv rg +dv rh +dv ri +dv rj +dv rk +dv rl +dv rm +dv rn +dv ro +dv rq +dv rr +dv rs +dv rt +dv ru +dv rv +dv rw +dv rx +dv ry +dv rz +dv raa +dv rab +dv rac +dv rad +dv ref +dv ref2 +dv ref3 +dv binp +dv binr +dv null +dv rae +dv aae + +quit Index: lldb/lit/SymbolFile/NativePDB/Inputs/function-types-calling-conv.lldbinit =================================================================== --- /dev/null +++ lldb/lit/SymbolFile/NativePDB/Inputs/function-types-calling-conv.lldbinit @@ -0,0 +1,7 @@ +target variable sfn +target variable ffn +target variable tfn +target variable cfn +target variable vfn + +quit Index: lldb/lit/SymbolFile/NativePDB/Inputs/function-types-classes.lldbinit =================================================================== --- /dev/null +++ lldb/lit/SymbolFile/NativePDB/Inputs/function-types-classes.lldbinit @@ -0,0 +1,12 @@ +target variable a +target variable b +target variable c +target variable d +target variable e +target variable f +target variable g +target variable h +target variable i +target variable incomplete + +quit Index: lldb/lit/SymbolFile/NativePDB/function-types-builtins.cpp =================================================================== --- /dev/null +++ lldb/lit/SymbolFile/NativePDB/function-types-builtins.cpp @@ -0,0 +1,215 @@ +// clang-format off +// REQUIRES: lld + +// RUN: clang-cl /Z7 /GS- /GR- /c -Xclang -fkeep-static-consts /Fo%t.obj -- %s +// RUN: lld-link /DEBUG /nodefaultlib /entry:main /OUT:%t.exe /PDB:%t.pdb -- %t.obj +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 lldb -f %t.exe -s \ +// RUN: %p/Inputs/function-types-builtins.lldbinit | FileCheck %s + +// Test that we can display function signatures with simple builtin +// and pointer types. We do this by using `target variable` in lldb +// with global variables of type ptr-to-function or reference-to-function. +// This technique in general allows us to exercise most of LLDB's type +// system without a running process. + +template +struct MakeResult { + static T result() { + return T{}; + } +}; + +template +struct MakeResult { + static T& result() { + static T t; + return t; + } +}; + +template +struct MakeResult { + static T&& result() { + static T t; + return static_cast(t); + } +}; + + +void nullary() {} + +template +void unary(Arg) { } + +template +Ret unaryret() { return MakeResult::result(); } + +template +void binary(A1, A2) { } + +int varargs(int, int, ...) { return 0; } + +// Make sure to test every builtin type at least once for completeness. We +// test these in the globals-fundamentals.cpp when they are the types of +// variables but it's possible to imagine a situation where things behave +// differently as function arguments or return values than they do with +// global variables. + +// some interesting cases with argument types. +auto aa = &unary; +// CHECK: (void (*)(bool)) aa = {{.*}} +auto ab = &unary; +// CHECK: (void (*)(char)) ab = {{.*}} +auto ac = &unary; +// CHECK: (void (*)(signed char)) ac = {{.*}} +auto ad = &unary; +// CHECK: (void (*)(unsigned char)) ad = {{.*}} +auto ae = &unary; +// CHECK: (void (*)(char16_t)) ae = {{.*}} +auto af = &unary; +// CHECK: (void (*)(char32_t)) af = {{.*}} +auto ag = &unary; +// CHECK: (void (*)(wchar_t)) ag = {{.*}} +auto ah = &unary; +// CHECK: (void (*)(short)) ah = {{.*}} +auto ai = &unary; +// CHECK: (void (*)(unsigned short)) ai = {{.*}} +auto aj = &unary; +// CHECK: (void (*)(int)) aj = {{.*}} +auto ak = &unary; +// CHECK: (void (*)(unsigned int)) ak = {{.*}} +auto al = &unary; +// CHECK: (void (*)(long)) al = {{.*}} +auto am = &unary; +// CHECK: (void (*)(unsigned long)) am = {{.*}} +auto an = &unary; +// CHECK: (void (*)(long long)) an = {{.*}} +auto ao = &unary; +// CHECK: (void (*)(unsigned long long)) ao = {{.*}} +auto aq = &unary; +// CHECK: (void (*)(float)) aq = {{.*}} +auto ar = &unary; +// CHECK: (void (*)(double)) ar = {{.*}} + +auto as = &unary; +// CHECK: (void (*)(int *)) as = {{.*}} +auto at = &unary; +// CHECK: (void (*)(int **)) at = {{.*}} +auto au = &unary; +// CHECK: (void (*)(int &)) au = {{.*}} +auto av = &unary; +// CHECK: (void (*)(int &&)) av = {{.*}} +auto aw = &unary; +// CHECK: (void (*)(const int *)) aw = {{.*}} +auto ax = &unary; +// CHECK: (void (*)(volatile int *)) ax = {{.*}} +auto ay = &unary; +// CHECK: (void (*)(const volatile int *)) ay = {{.*}} +auto az = &unary; +// CHECK: (void (*)(void *&)) az = {{.*}} +auto aaa = &unary; +// CHECK: (void (*)(int (&)[5])) aaa = {{.*}} +auto aab = &unary; +// CHECK: (void (*)(int (*)[5])) aab = {{.*}} +auto aac = &unary; +// CHECK: (void (*)(int (&&)[5])) aac = {{.*}} +auto aad = &unary; +// CHECK: (void (*)(int (*)[5])) aad = {{.*}} + + +// same test cases with return values, note we can't overload on return type +// so we need to use a different instantiation each time. +auto ra = &unaryret; +// CHECK: (bool (*)()) ra = {{.*}} +auto rb = &unaryret; +// CHECK: (char (*)()) rb = {{.*}} +auto rc = &unaryret; +// CHECK: (signed char (*)()) rc = {{.*}} +auto rd = &unaryret; +// CHECK: (unsigned char (*)()) rd = {{.*}} +auto re = &unaryret; +// CHECK: (char16_t (*)()) re = {{.*}} +auto rf = &unaryret; +// CHECK: (char32_t (*)()) rf = {{.*}} +auto rg = &unaryret; +// CHECK: (wchar_t (*)()) rg = {{.*}} +auto rh = &unaryret; +// CHECK: (short (*)()) rh = {{.*}} +auto ri = &unaryret; +// CHECK: (unsigned short (*)()) ri = {{.*}} +auto rj = &unaryret; +// CHECK: (int (*)()) rj = {{.*}} +auto rk = &unaryret; +// CHECK: (unsigned int (*)()) rk = {{.*}} +auto rl = &unaryret; +// CHECK: (long (*)()) rl = {{.*}} +auto rm = &unaryret; +// CHECK: (unsigned long (*)()) rm = {{.*}} +auto rn = &unaryret; +// CHECK: (long long (*)()) rn = {{.*}} +auto ro = &unaryret; +// CHECK: (unsigned long long (*)()) ro = {{.*}} +auto rq = &unaryret; +// CHECK: (float (*)()) rq = {{.*}} +auto rr = &unaryret; +// CHECK: (double (*)()) rr = {{.*}} + +auto rs = &unaryret; +// CHECK: (int *(*)()) rs = {{.*}} +auto rt = &unaryret; +// CHECK: (int **(*)()) rt = {{.*}} +auto ru = &unaryret; +// CHECK: (int &(*)()) ru = {{.*}} +auto rv = &unaryret; +// CHECK: (int &&(*)()) rv = {{.*}} +auto rw = &unaryret; +// CHECK: (const int *(*)()) rw = {{.*}} +auto rx = &unaryret; +// CHECK: (volatile int *(*)()) rx = {{.*}} +auto ry = &unaryret; +// CHECK: (const volatile int *(*)()) ry = {{.*}} +auto rz = &unaryret; +// CHECK: (void *&(*)()) rz = {{.*}} + +// FIXME: This output doesn't really look correct. It should probably be +// formatting this as `int(&)[5] (*)()`. +auto raa = &unaryret; +// CHECK: (int (&(*)())[5]) raa = {{.*}} +auto rab = &unaryret; +// CHECK: (int (*(*)())[5]) rab = {{.*}} +auto rac = &unaryret; +// CHECK: (int (&&(*)())[5]) rac = {{.*}} +auto rad = &unaryret; +// CHECK: (int (*const (*)())[5]) rad = {{.*}} + + + +// Function references, we only need a couple of these since most of the +// interesting cases are already tested. +auto &ref = unary; +// CHECK: (void (&)(bool)) ref = {{.*}} (&::ref = ) +auto &ref2 = unary; +// CHECK: (void (&)(volatile int *)) ref2 = {{.*}} (&::ref2 = ) +auto &ref3 = varargs; +// CHECK: (int (&)(int, int, ...)) ref3 = {{.*}} (&::ref3 = ) + +// Multiple arguments, as before, just something to make sure it works. +auto binp = &binary; +// CHECK: (void (*)(int *, const int *)) binp = {{.*}} +auto &binr = binary; +// CHECK: (void (&)(int *, const int *)) binr = {{.*}} (&::binr = ) + +// And finally, a function with no arguments. +auto null = &nullary; +// CHECK: (void (*)()) null = {{.*}} + +// FIXME: These currently don't work because clang-cl emits incorrect debug info +// for std::nullptr_t. We should fix these in clang-cl. +auto rae = &unaryret; +// CHECK: rae = +auto aae = &unary; +// CHECK: (void (*)(...)) aae = {{.*}} + +int main(int argc, char **argv) { + return 0; +} Index: lldb/lit/SymbolFile/NativePDB/function-types-calling-conv.cpp =================================================================== --- /dev/null +++ lldb/lit/SymbolFile/NativePDB/function-types-calling-conv.cpp @@ -0,0 +1,33 @@ +// clang-format off +// REQUIRES: lld + +// RUN: clang-cl -m32 /Z7 /GS- /GR- /c -Xclang -fkeep-static-consts /Fo%t.obj -- %s +// RUN: lld-link /DEBUG /nodefaultlib /entry:main /OUT:%t.exe /PDB:%t.pdb -- %t.obj +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 lldb -f %t.exe -s \ +// RUN: %p/Inputs/function-types-calling-conv.lldbinit | FileCheck %s + + +void __stdcall StdcallFn() {} +void __fastcall FastcallFn() {} +void __thiscall ThiscallFn() {} +void __cdecl CdeclFn() {} +void __vectorcall VectorcallFn() {} + +auto sfn = &StdcallFn; +// CHECK: (void (*)() __attribute__((stdcall))) sfn = {{.*}} + +auto ffn = &FastcallFn; +// CHECK: (void (*)() __attribute__((fastcall))) ffn = {{.*}} + +auto tfn = &ThiscallFn; +// CHECK: (void (*)() __attribute__((thiscall))) tfn = {{.*}} + +auto cfn = &CdeclFn; +// CHECK: (void (*)()) cfn = {{.*}} + +auto vfn = &VectorcallFn; +// CHECK: (void (*)() __attribute__((vectorcall))) vfn = {{.*}} + +int main(int argc, char **argv) { + return 0; +} \ No newline at end of file Index: lldb/lit/SymbolFile/NativePDB/function-types-classes.cpp =================================================================== --- /dev/null +++ lldb/lit/SymbolFile/NativePDB/function-types-classes.cpp @@ -0,0 +1,118 @@ +// clang-format off +// REQUIRES: lld + +// Test that we can display function signatures with class types. +// RUN: clang-cl /Z7 /GS- /GR- /c -Xclang -fkeep-static-consts /Fo%t.obj -- %s +// RUN: lld-link /DEBUG /nodefaultlib /entry:main /OUT:%t.exe /PDB:%t.pdb -- %t.obj +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 lldb -f %t.exe -s \ +// RUN: %p/Inputs/function-types-classes.lldbinit | FileCheck %s + +// This is just some unimportant helpers needed so that we can get reference and +// rvalue-reference types into return values. +template +struct MakeResult { + static T result() { + return T{}; + } +}; + +template +struct MakeResult { + static T& result() { + static T t; + return t; + } +}; + +template +struct MakeResult { + static T&& result() { + static T t; + return static_cast(t); + } +}; + + +template +R nullary() { return MakeResult::result(); } + +template +R three(A a, B b) { return MakeResult::result(); } + +template +R four(A a, B b, C c) { return MakeResult::result(); } + +struct S {}; +class C {}; +union U {}; +enum E {}; + +namespace A { + namespace B { + // NS::NS + struct S { }; + } + + struct C { + // NS::Struct + struct S {}; + }; +} + +struct B { + struct A { + // Struct::Struct + struct S {}; + }; +}; + +template +struct TC {}; + +// const and volatile modifiers +auto a = &four; +// CHECK: (S (*)(C *, U &, E &&)) a = {{.*}} +auto b = &four; +// CHECK: (E (*)(const S *, const C &, const U &&)) b = {{.*}} +auto c = &four; +// CHECK: (U (*)(volatile E *, volatile S &, volatile C &&)) c = {{.*}} +auto d = &four; +// CHECK: (C (*)(const volatile U *, const volatile E &, const volatile S &&)) d = {{.*}} + +// classes nested in namespaces and inner classes + +// FIXME: LLDB with native pdb plugin doesn't currently resolve nested names +// correctly, because it requires creating clang::NamespaceDecl or +// clang::RecordDecl for the outer namespace or classes. PDB doesn't contain +// sufficient information to distinguish namespace scopes from nested class +// scopes, so the best we can hope for is a heuristic reconstruction of the +// clang AST based on demangling the type's unique name. However, this is +// as-yet unimplemented in the native PDB plugin, so for now all of these will +// all just look like `S` when LLDB prints them. +auto e = &three; +// CHECK: (S *(*)(S *, S &)) e = {{.*}} +auto f = &three; +// CHECK: (S &(*)(S *, S *)) f = {{.*}} +auto g = &three; +// CHECK: (S *(*)(S &, S *)) g = {{.*}} + +// parameter types that are themselves template instantiations. +auto h = &four, TC, TC>, TC>; +// Note the awkward space in TC >. This is because this is how template +// instantiations are emitted by the compiler, as the fully instantiated name. +// Only via reconstruction of the AST through the mangled type name (see above +// comment) can we hope to do better than this). +// CHECK: (TC (*)(TC, TC >, S>)) h = {{.*}} + +auto i = &nullary; +// CHECK: (S (*)()) i = {{.*}} + + +// Make sure we can handle types that don't have complete debug info. +struct Incomplete; +auto incomplete = &three; +// CHECK: (Incomplete *(*)(Incomplete **, const Incomplete *)) incomplete = {{.*}} + +int main(int argc, char **argv) { + return 0; +} Index: lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp =================================================================== --- lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp +++ lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp @@ -92,6 +92,8 @@ return PDB_SymType::PointerType; case LF_ENUM: return PDB_SymType::Enum; + case LF_PROCEDURE: + return PDB_SymType::FunctionSig; default: lldbassert(false && "Invalid type record kind!"); } @@ -361,4 +363,4 @@ assert(offset + 2 <= name.size()); return name.substr(offset + 2); -} \ No newline at end of file +} Index: lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h =================================================================== --- lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h +++ lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h @@ -172,6 +172,8 @@ const llvm::codeview::UnionRecord &ur); lldb::TypeSP CreateArrayType(PdbSymUid type_uid, const llvm::codeview::ArrayRecord &ar); + lldb::TypeSP CreateProcedureType(PdbSymUid type_uid, + const llvm::codeview::ProcedureRecord &pr); lldb::TypeSP CreateClassStructUnion(PdbSymUid type_uid, llvm::StringRef name, size_t size, clang::TagTypeKind ttk, Index: lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp =================================================================== --- lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -49,6 +49,8 @@ #include "llvm/Support/ErrorOr.h" #include "llvm/Support/MemoryBuffer.h" +#include "Plugins/Language/CPlusPlus/CPlusPlusNameParser.h" + #include "PdbSymUid.h" #include "PdbUtil.h" #include "UdtRecordCompleter.h" @@ -394,6 +396,12 @@ return GetPdbSymType(tpi, LookThroughModifierRecord(cvt)); } +static bool IsCVarArgsFunction(llvm::ArrayRef args) { + if (args.empty()) + return false; + return args.back() == TypeIndex::None(); +} + static clang::TagTypeKind TranslateUdtKind(const TagRecord &cr) { switch (cr.Kind) { case TypeRecordKind::Class: @@ -412,6 +420,32 @@ } } +static llvm::Optional +TranslateCallingConvention(llvm::codeview::CallingConvention conv) { + using CC = llvm::codeview::CallingConvention; + switch (conv) { + + case CC::NearC: + case CC::FarC: + return clang::CallingConv::CC_C; + case CC::NearPascal: + case CC::FarPascal: + return clang::CallingConv::CC_X86Pascal; + case CC::NearFast: + case CC::FarFast: + return clang::CallingConv::CC_X86FastCall; + case CC::NearStdCall: + case CC::FarStdCall: + return clang::CallingConv::CC_X86StdCall; + case CC::ThisCall: + return clang::CallingConv::CC_X86ThisCall; + case CC::NearVector: + return clang::CallingConv::CC_X86VectorCall; + default: + return llvm::None; + } +} + void SymbolFileNativePDB::Initialize() { PluginManager::RegisterPlugin(GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, @@ -540,7 +574,6 @@ PdbSymUid sig_uid = PdbSymUid::makeTypeSymId(PDB_SymType::FunctionSig, TypeIndex{0}, false); Mangled mangled(getSymbolName(sym_record)); - FunctionSP func_sp = std::make_shared( sc.comp_unit, func_uid.toOpaqueId(), sig_uid.toOpaqueId(), mangled, func_type, func_range); @@ -598,6 +631,8 @@ lldb::TypeSP SymbolFileNativePDB::CreatePointerType( PdbSymUid type_uid, const llvm::codeview::PointerRecord &pr) { TypeSP pointee = GetOrCreateType(pr.ReferentType); + if (!pointee) + return nullptr; CompilerType pointee_ct = pointee->GetForwardCompilerType(); lldbassert(pointee_ct); Declaration decl; @@ -670,7 +705,8 @@ return nullptr; lldb::BasicType bt = GetCompilerTypeForSimpleKind(ti.getSimpleKind()); - lldbassert(bt != lldb::eBasicTypeInvalid); + if (bt == lldb::eBasicTypeInvalid) + return nullptr; CompilerType ct = m_clang->GetBasicType(bt); size_t size = GetTypeSizeForSimpleKind(ti.getSimpleKind()); @@ -687,10 +723,6 @@ PdbSymUid type_uid, llvm::StringRef name, size_t size, clang::TagTypeKind ttk, clang::MSInheritanceAttr::Spelling inheritance) { - // Some UDT with trival ctor has zero length. Just ignore. - if (size == 0) - return nullptr; - // Ignore unnamed-tag UDTs. name = DropNameScope(name); if (name.empty()) @@ -792,6 +824,49 @@ return array_sp; } +TypeSP SymbolFileNativePDB::CreateProcedureType(PdbSymUid type_uid, + const ProcedureRecord &pr) { + TpiStream &stream = m_index->tpi(); + CVType args_cvt = stream.getType(pr.ArgumentList); + ArgListRecord args; + llvm::cantFail( + TypeDeserializer::deserializeAs(args_cvt, args)); + + llvm::ArrayRef arg_indices = llvm::makeArrayRef(args.ArgIndices); + bool is_variadic = IsCVarArgsFunction(arg_indices); + if (is_variadic) + arg_indices = arg_indices.drop_back(); + + std::vector arg_list; + arg_list.reserve(arg_list.size()); + + for (TypeIndex arg_index : arg_indices) { + TypeSP arg_sp = GetOrCreateType(arg_index); + if (!arg_sp) + return nullptr; + arg_list.push_back(arg_sp->GetFullCompilerType()); + } + + TypeSP return_type_sp = GetOrCreateType(pr.ReturnType); + if (!return_type_sp) + return nullptr; + + llvm::Optional cc = + TranslateCallingConvention(pr.CallConv); + if (!cc) + return nullptr; + + CompilerType return_ct = return_type_sp->GetFullCompilerType(); + CompilerType func_sig_ast_type = m_clang->CreateFunctionType( + return_ct, arg_list.data(), arg_list.size(), is_variadic, 0, *cc); + + Declaration decl; + return std::make_shared( + type_uid.toOpaqueId(), this, ConstString(), 0, nullptr, LLDB_INVALID_UID, + lldb_private::Type::eEncodingIsUID, decl, func_sig_ast_type, + lldb_private::Type::eResolveStateFull); +} + TypeSP SymbolFileNativePDB::CreateType(PdbSymUid type_uid) { const PdbTypeSymId &tsid = type_uid.asTypeSym(); TypeIndex index(tsid.index); @@ -840,6 +915,12 @@ return CreateArrayType(type_uid, ar); } + if (cvt.kind() == LF_PROCEDURE) { + ProcedureRecord pr; + llvm::cantFail(TypeDeserializer::deserializeAs(cvt, pr)); + return CreateProcedureType(type_uid, pr); + } + return nullptr; } @@ -858,7 +939,7 @@ auto expected_full_ti = m_index->tpi().findFullDeclForForwardRef(ti); if (!expected_full_ti) llvm::consumeError(expected_full_ti.takeError()); - else { + else if (*expected_full_ti != ti) { full_decl_uid = PdbSymUid::makeTypeSymId( type_uid.tag(), *expected_full_ti, type_id.is_ipi); @@ -880,6 +961,8 @@ PdbSymUid best_uid = full_decl_uid ? *full_decl_uid : type_uid; TypeSP result = CreateType(best_uid); + if (!result) + return nullptr; m_types[best_uid.toOpaqueId()] = result; // If we had both a forward decl and a full decl, make both point to the new // type. Index: llvm/include/llvm/DebugInfo/CodeView/SymbolDeserializer.h =================================================================== --- llvm/include/llvm/DebugInfo/CodeView/SymbolDeserializer.h +++ llvm/include/llvm/DebugInfo/CodeView/SymbolDeserializer.h @@ -47,7 +47,7 @@ return Error::success(); } template static Expected deserializeAs(CVSymbol Symbol) { - T Record(Symbol.kind()); + T Record(static_cast(Symbol.kind())); if (auto EC = deserializeAs(Symbol, Record)) return std::move(EC); return Record;