forked from OSchip/llvm-project
[NativePDB] Improved support for nested type reconstruction.
In a previous patch, we pre-processed the TPI stream in order to build the reverse mapping from nested type -> parent type so that we could accurately reconstruct a DeclContext hierarchy. However, there were some issues. An LF_NESTTYPE record is really just a typedef, so although it happens to be used to indicate the name of the nested type and referring to the global record which defines the type, it is also used for every other kind of nested typedef. When we rebuild the DeclContext hierarchy, we want it to be as accurate as possible, which means that if we have something like: struct A { struct B {}; using C = B; }; We don't want to create two CXXRecordDecls in the AST each with the exact same definition. We just want to create one for B and then define C as an alias to B. Previously, however, it would not be able to distinguish between the two cases and it would treat A::B and A::C as being two classes each with separate definitions. We address the first half of improving the pre-processing logic so that only actual definitions are treated this way. Later, in a followup patch, we can handle the case of nested typedefs since we're already going to be enumerating the field list anyway and this patch introduces the general framework for distinguishing between the two cases. Differential Revision: https://reviews.llvm.org/D54357 llvm-svn: 346786
This commit is contained in:
parent
c2728bc932
commit
03a24052f3
|
@ -0,0 +1,12 @@
|
|||
settings set auto-one-line-summaries false
|
||||
|
||||
target variable -T GlobalA
|
||||
target variable -T GlobalB
|
||||
target variable -T GlobalC
|
||||
target variable -T GlobalD
|
||||
target variable -T GlobalE
|
||||
target variable -T GlobalF
|
||||
target variable -T GlobalG
|
||||
target variable -T GlobalH
|
||||
|
||||
target modules dump ast
|
|
@ -0,0 +1,139 @@
|
|||
// clang-format off
|
||||
// REQUIRES: lld
|
||||
|
||||
// Test various interesting cases for AST reconstruction.
|
||||
// RUN: clang-cl /Z7 /GS- /GR- -Xclang -fkeep-static-consts /c /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/nested-types.lldbinit 2>&1 | FileCheck %s
|
||||
|
||||
struct S {
|
||||
struct NestedStruct {
|
||||
int A = 0;
|
||||
int B = 1;
|
||||
};
|
||||
int C = 2;
|
||||
int D = 3;
|
||||
};
|
||||
struct T {
|
||||
using NestedTypedef = int;
|
||||
using NestedTypedef2 = S;
|
||||
|
||||
struct NestedStruct {
|
||||
int E = 4;
|
||||
int F = 5;
|
||||
};
|
||||
|
||||
using NestedStructAlias = NestedStruct;
|
||||
using NST = S::NestedStruct;
|
||||
|
||||
NestedTypedef NT = 4;
|
||||
|
||||
using U = struct {
|
||||
int G = 6;
|
||||
int H = 7;
|
||||
};
|
||||
};
|
||||
|
||||
template<typename Param>
|
||||
class U {
|
||||
public:
|
||||
// See llvm.org/pr39607. clang-cl currently doesn't emit an important debug
|
||||
// info record for nested template instantiations, so we can't reconstruct
|
||||
// a proper DeclContext hierarchy for these. As such, U<X>::V<Y> will show up
|
||||
// in the global namespace.
|
||||
template<typename Param>
|
||||
struct V {
|
||||
Param I = 8;
|
||||
Param J = 9;
|
||||
|
||||
using W = T::NestedTypedef;
|
||||
using X = U<int>;
|
||||
};
|
||||
|
||||
struct W {
|
||||
Param M = 12;
|
||||
Param N = 13;
|
||||
};
|
||||
Param K = 10;
|
||||
Param L = 11;
|
||||
using Y = V<int>;
|
||||
using Z = V<T>;
|
||||
};
|
||||
|
||||
constexpr S GlobalA;
|
||||
constexpr S::NestedStruct GlobalB;
|
||||
constexpr T GlobalC;
|
||||
constexpr T::NestedStruct GlobalD;
|
||||
constexpr T::U GlobalE;
|
||||
constexpr U<int> GlobalF;
|
||||
constexpr U<int>::V<int> GlobalG;
|
||||
constexpr U<int>::W GlobalH;
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CHECK: (lldb) target variable -T GlobalA
|
||||
// CHECK: (const S) GlobalA = {
|
||||
// CHECK: (int) C = 2
|
||||
// CHECK: (int) D = 3
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target variable -T GlobalB
|
||||
// CHECK: (const S::NestedStruct) GlobalB = {
|
||||
// CHECK: (int) A = 0
|
||||
// CHECK: (int) B = 1
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target variable -T GlobalC
|
||||
// CHECK: (const T) GlobalC = {
|
||||
// CHECK: (int) NT = 4
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target variable -T GlobalD
|
||||
// CHECK: (const T::NestedStruct) GlobalD = {
|
||||
// CHECK: (int) E = 4
|
||||
// CHECK: (int) F = 5
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target variable -T GlobalE
|
||||
// CHECK: (const T::U) GlobalE = {
|
||||
// CHECK: (int) G = 6
|
||||
// CHECK: (int) H = 7
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target variable -T GlobalF
|
||||
// CHECK: (const U<int>) GlobalF = {
|
||||
// CHECK: (int) K = 10
|
||||
// CHECK: (int) L = 11
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target variable -T GlobalG
|
||||
// CHECK: (const U<int>::V<int>) GlobalG = {
|
||||
// CHECK: (int) I = 8
|
||||
// CHECK: (int) J = 9
|
||||
// CHECK: }
|
||||
// CHECK: (lldb) target modules dump ast
|
||||
// CHECK: Dumping clang ast for 1 modules.
|
||||
// CHECK: TranslationUnitDecl {{.*}}
|
||||
// CHECK: |-CXXRecordDecl {{.*}} struct S definition
|
||||
// CHECK: | |-FieldDecl {{.*}} C 'int'
|
||||
// CHECK: | |-FieldDecl {{.*}} D 'int'
|
||||
// CHECK: | `-CXXRecordDecl {{.*}} struct NestedStruct definition
|
||||
// CHECK: | |-FieldDecl {{.*}} A 'int'
|
||||
// CHECK: | `-FieldDecl {{.*}} B 'int'
|
||||
// CHECK: |-CXXRecordDecl {{.*}} struct T definition
|
||||
// CHECK: | |-FieldDecl {{.*}} NT 'int'
|
||||
// CHECK: | |-CXXRecordDecl {{.*}} struct NestedStruct definition
|
||||
// CHECK: | | |-FieldDecl {{.*}} E 'int'
|
||||
// CHECK: | | `-FieldDecl {{.*}} F 'int'
|
||||
// CHECK: | `-CXXRecordDecl {{.*}} struct U definition
|
||||
// CHECK: | |-FieldDecl {{.*}} G 'int'
|
||||
// CHECK: | `-FieldDecl {{.*}} H 'int'
|
||||
// CHECK: |-CXXRecordDecl {{.*}} class U<int> definition
|
||||
// CHECK: | |-FieldDecl {{.*}} K 'int'
|
||||
// CHECK: | |-FieldDecl {{.*}} L 'int'
|
||||
// CHECK: | `-CXXRecordDecl {{.*}} struct W definition
|
||||
// CHECK: | |-FieldDecl {{.*}} M 'int'
|
||||
// CHECK: | `-FieldDecl {{.*}} N 'int'
|
||||
// CHECK: |-CXXRecordDecl {{.*}} struct U<int>::V<int> definition
|
||||
// CHECK: | |-FieldDecl {{.*}} I 'int'
|
||||
// CHECK: | `-FieldDecl {{.*}} J 'int'
|
|
@ -535,6 +535,49 @@ void SymbolFileNativePDB::InitializeObject() {
|
|||
lldbassert(m_clang);
|
||||
}
|
||||
|
||||
static llvm::Optional<CVTagRecord>
|
||||
GetNestedTagRecord(const NestedTypeRecord &Record, const CVTagRecord &parent,
|
||||
TpiStream &tpi) {
|
||||
// An LF_NESTTYPE is essentially a nested typedef / using declaration, but it
|
||||
// is also used to indicate the primary definition of a nested class. That is
|
||||
// to say, if you have:
|
||||
// struct A {
|
||||
// struct B {};
|
||||
// using C = B;
|
||||
// };
|
||||
// Then in the debug info, this will appear as:
|
||||
// LF_STRUCTURE `A::B` [type index = N]
|
||||
// LF_STRUCTURE `A`
|
||||
// LF_NESTTYPE [name = `B`, index = N]
|
||||
// LF_NESTTYPE [name = `C`, index = N]
|
||||
// In order to accurately reconstruct the decl context hierarchy, we need to
|
||||
// know which ones are actual definitions and which ones are just aliases.
|
||||
|
||||
// If it's a simple type, then this is something like `using foo = int`.
|
||||
if (Record.Type.isSimple())
|
||||
return llvm::None;
|
||||
|
||||
// If it's an inner definition, then treat whatever name we have here as a
|
||||
// single component of a mangled name. So we can inject it into the parent's
|
||||
// mangled name to see if it matches.
|
||||
CVTagRecord child = CVTagRecord::create(tpi.getType(Record.Type));
|
||||
std::string qname = parent.asTag().getUniqueName();
|
||||
if (qname.size() < 4 || child.asTag().getUniqueName().size() < 4)
|
||||
return llvm::None;
|
||||
|
||||
// qname[3] is the tag type identifier (struct, class, union, etc). Since the
|
||||
// inner tag type is not necessarily the same as the outer tag type, re-write
|
||||
// it to match the inner tag type.
|
||||
qname[3] = child.asTag().getUniqueName()[3];
|
||||
std::string piece = Record.Name;
|
||||
piece.push_back('@');
|
||||
qname.insert(4, std::move(piece));
|
||||
if (qname != child.asTag().UniqueName)
|
||||
return llvm::None;
|
||||
|
||||
return std::move(child);
|
||||
}
|
||||
|
||||
void SymbolFileNativePDB::PreprocessTpiStream() {
|
||||
LazyRandomTypeCollection &types = m_index->tpi().typeCollection();
|
||||
|
||||
|
@ -552,19 +595,27 @@ void SymbolFileNativePDB::PreprocessTpiStream() {
|
|||
|
||||
struct ProcessTpiStream : public TypeVisitorCallbacks {
|
||||
ProcessTpiStream(PdbIndex &index, TypeIndex parent,
|
||||
const CVTagRecord &parent_cvt,
|
||||
llvm::DenseMap<TypeIndex, TypeIndex> &parents)
|
||||
: index(index), parents(parents), parent(parent) {}
|
||||
: index(index), parents(parents), parent(parent),
|
||||
parent_cvt(parent_cvt) {}
|
||||
|
||||
PdbIndex &index;
|
||||
llvm::DenseMap<TypeIndex, TypeIndex> &parents;
|
||||
TypeIndex parent;
|
||||
const CVTagRecord &parent_cvt;
|
||||
|
||||
llvm::Error visitKnownMember(CVMemberRecord &CVR,
|
||||
NestedTypeRecord &Record) override {
|
||||
parents[Record.Type] = parent;
|
||||
CVType child = index.tpi().getType(Record.Type);
|
||||
if (!IsForwardRefUdt(child))
|
||||
llvm::Optional<CVTagRecord> tag =
|
||||
GetNestedTagRecord(Record, parent_cvt, index.tpi());
|
||||
if (!tag)
|
||||
return llvm::ErrorSuccess();
|
||||
|
||||
parents[Record.Type] = parent;
|
||||
if (!tag->asTag().isForwardRef())
|
||||
return llvm::ErrorSuccess();
|
||||
|
||||
llvm::Expected<TypeIndex> full_decl =
|
||||
index.tpi().findFullDeclForForwardRef(Record.Type);
|
||||
if (!full_decl) {
|
||||
|
@ -577,7 +628,7 @@ void SymbolFileNativePDB::PreprocessTpiStream() {
|
|||
};
|
||||
|
||||
CVType field_list = m_index->tpi().getType(tag.asTag().FieldList);
|
||||
ProcessTpiStream process(*m_index, *ti, m_parent_types);
|
||||
ProcessTpiStream process(*m_index, *ti, tag, m_parent_types);
|
||||
llvm::Error error = visitMemberRecordStream(field_list.data(), process);
|
||||
if (error)
|
||||
llvm::consumeError(std::move(error));
|
||||
|
@ -792,6 +843,16 @@ static std::string RenderDemanglerNode(llvm::ms_demangle::Node *n) {
|
|||
return {OS.getBuffer()};
|
||||
}
|
||||
|
||||
static bool
|
||||
AnyScopesHaveTemplateParams(llvm::ArrayRef<llvm::ms_demangle::Node *> scopes) {
|
||||
for (llvm::ms_demangle::Node *n : scopes) {
|
||||
auto *idn = static_cast<llvm::ms_demangle::IdentifierNode *>(n);
|
||||
if (idn->TemplateParams)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<clang::DeclContext *, std::string>
|
||||
SymbolFileNativePDB::CreateDeclInfoForType(const TagRecord &record,
|
||||
TypeIndex ti) {
|
||||
|
@ -817,6 +878,14 @@ SymbolFileNativePDB::CreateDeclInfoForType(const TagRecord &record,
|
|||
if (scopes.empty())
|
||||
return {context, uname};
|
||||
|
||||
// If there is no parent in the debug info, but some of the scopes have
|
||||
// template params, then this is a case of bad debug info. See, for
|
||||
// example, llvm.org/pr39607. We don't want to create an ambiguity between
|
||||
// a NamespaceDecl and a CXXRecordDecl, so instead we create a class at
|
||||
// global scope with the fully qualified name.
|
||||
if (AnyScopesHaveTemplateParams(scopes))
|
||||
return {context, record.Name};
|
||||
|
||||
for (llvm::ms_demangle::Node *scope : scopes) {
|
||||
auto *nii = static_cast<llvm::ms_demangle::NamedIdentifierNode *>(scope);
|
||||
std::string str = RenderDemanglerNode(nii);
|
||||
|
|
|
@ -89,6 +89,8 @@ uint32_t LazyRandomTypeCollection::getOffsetOfType(TypeIndex Index) {
|
|||
}
|
||||
|
||||
CVType LazyRandomTypeCollection::getType(TypeIndex Index) {
|
||||
assert(!Index.isSimple());
|
||||
|
||||
auto EC = ensureTypeExists(Index);
|
||||
error(std::move(EC));
|
||||
assert(contains(Index));
|
||||
|
@ -97,6 +99,9 @@ CVType LazyRandomTypeCollection::getType(TypeIndex Index) {
|
|||
}
|
||||
|
||||
Optional<CVType> LazyRandomTypeCollection::tryGetType(TypeIndex Index) {
|
||||
if (Index.isSimple())
|
||||
return None;
|
||||
|
||||
if (auto EC = ensureTypeExists(Index)) {
|
||||
consumeError(std::move(EC));
|
||||
return None;
|
||||
|
@ -151,6 +156,7 @@ Error LazyRandomTypeCollection::ensureTypeExists(TypeIndex TI) {
|
|||
}
|
||||
|
||||
void LazyRandomTypeCollection::ensureCapacityFor(TypeIndex Index) {
|
||||
assert(!Index.isSimple());
|
||||
uint32_t MinSize = Index.toArrayIndex() + 1;
|
||||
|
||||
if (MinSize <= capacity())
|
||||
|
@ -163,6 +169,7 @@ void LazyRandomTypeCollection::ensureCapacityFor(TypeIndex Index) {
|
|||
}
|
||||
|
||||
Error LazyRandomTypeCollection::visitRangeForType(TypeIndex TI) {
|
||||
assert(!TI.isSimple());
|
||||
if (PartialOffsets.empty())
|
||||
return fullScanForType(TI);
|
||||
|
||||
|
@ -217,6 +224,7 @@ Optional<TypeIndex> LazyRandomTypeCollection::getNext(TypeIndex Prev) {
|
|||
}
|
||||
|
||||
Error LazyRandomTypeCollection::fullScanForType(TypeIndex TI) {
|
||||
assert(!TI.isSimple());
|
||||
assert(PartialOffsets.empty());
|
||||
|
||||
TypeIndex CurrentTI = TypeIndex::fromArrayIndex(0);
|
||||
|
|
|
@ -215,6 +215,7 @@ TpiStream::findFullDeclForForwardRef(TypeIndex ForwardRefTI) const {
|
|||
}
|
||||
|
||||
codeview::CVType TpiStream::getType(codeview::TypeIndex Index) {
|
||||
assert(!Index.isSimple());
|
||||
return Types->getType(Index);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue