Skip to content

Commit 61c7a91

Browse files
committed
Commit to a primary definition for a class when we load its first
member. Previously, we wouldn't do this if the first member loaded is within a definition that's added to a class via an update record, which happens when template instantiation adds a class definition to a declaration that was imported from an AST file. This would lead to classes having member functions whose getParent returned a class declaration that wasn't the primary definition, which in turn caused the vtable builder to build broken vtables. I don't yet have a reduced testcase for the wrong-code bug here, because the setup required to get us into the broken state is very subtle, but have confirmed that this fixes it.
1 parent 4af01bf commit 61c7a91

File tree

2 files changed

+46
-24
lines changed

2 files changed

+46
-24
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ Improvements to Clang's diagnostics
9898

9999
Bug Fixes in This Version
100100
-------------------------
101+
- Fixed an issue where a class template specialization whose declaration is
102+
instantiated in one module and whose definition is instantiated in another
103+
module may end up with members associated with the wrong declaration of the
104+
class, which can result in miscompiles in some cases.
101105

102106
Bug Fixes to Compiler Builtins
103107
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ namespace clang {
181181
static void setAnonymousDeclForMerging(ASTReader &Reader, DeclContext *DC,
182182
unsigned Index, NamedDecl *D);
183183

184+
/// Commit to a primary definition of the class RD, which is known to be
185+
/// a definition of the class. We might not have read the definition data
186+
/// for it yet. If we haven't then allocate placeholder definition data
187+
/// now too.
188+
static CXXRecordDecl *getOrFakePrimaryClassDefinition(ASTReader &Reader,
189+
CXXRecordDecl *RD);
190+
184191
/// Results from loading a RedeclarableDecl.
185192
class RedeclarableResult {
186193
Decl *MergeWith;
@@ -598,7 +605,13 @@ void ASTDeclReader::VisitDecl(Decl *D) {
598605
auto *LexicalDC = readDeclAs<DeclContext>();
599606
if (!LexicalDC)
600607
LexicalDC = SemaDC;
601-
DeclContext *MergedSemaDC = Reader.MergedDeclContexts.lookup(SemaDC);
608+
// If the context is a class, we might not have actually merged it yet, in
609+
// the case where the definition comes from an update record.
610+
DeclContext *MergedSemaDC;
611+
if (auto *RD = dyn_cast<CXXRecordDecl>(SemaDC))
612+
MergedSemaDC = getOrFakePrimaryClassDefinition(Reader, RD);
613+
else
614+
MergedSemaDC = Reader.MergedDeclContexts.lookup(SemaDC);
602615
// Avoid calling setLexicalDeclContext() directly because it uses
603616
// Decl::getASTContext() internally which is unsafe during derialization.
604617
D->setDeclContextsImpl(MergedSemaDC ? MergedSemaDC : SemaDC, LexicalDC,
@@ -3198,36 +3211,41 @@ uint64_t ASTReader::getGlobalBitOffset(ModuleFile &M, uint64_t LocalOffset) {
31983211
return LocalOffset + M.GlobalBitOffset;
31993212
}
32003213

3214+
CXXRecordDecl *
3215+
ASTDeclReader::getOrFakePrimaryClassDefinition(ASTReader &Reader,
3216+
CXXRecordDecl *RD) {
3217+
// Try to dig out the definition.
3218+
auto *DD = RD->DefinitionData;
3219+
if (!DD)
3220+
DD = RD->getCanonicalDecl()->DefinitionData;
3221+
3222+
// If there's no definition yet, then DC's definition is added by an update
3223+
// record, but we've not yet loaded that update record. In this case, we
3224+
// commit to DC being the canonical definition now, and will fix this when
3225+
// we load the update record.
3226+
if (!DD) {
3227+
DD = new (Reader.getContext()) struct CXXRecordDecl::DefinitionData(RD);
3228+
RD->setCompleteDefinition(true);
3229+
RD->DefinitionData = DD;
3230+
RD->getCanonicalDecl()->DefinitionData = DD;
3231+
3232+
// Track that we did this horrible thing so that we can fix it later.
3233+
Reader.PendingFakeDefinitionData.insert(
3234+
std::make_pair(DD, ASTReader::PendingFakeDefinitionKind::Fake));
3235+
}
3236+
3237+
return DD->Definition;
3238+
}
3239+
32013240
/// Find the context in which we should search for previous declarations when
32023241
/// looking for declarations to merge.
32033242
DeclContext *ASTDeclReader::getPrimaryContextForMerging(ASTReader &Reader,
32043243
DeclContext *DC) {
32053244
if (auto *ND = dyn_cast<NamespaceDecl>(DC))
32063245
return ND->getOriginalNamespace();
32073246

3208-
if (auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
3209-
// Try to dig out the definition.
3210-
auto *DD = RD->DefinitionData;
3211-
if (!DD)
3212-
DD = RD->getCanonicalDecl()->DefinitionData;
3213-
3214-
// If there's no definition yet, then DC's definition is added by an update
3215-
// record, but we've not yet loaded that update record. In this case, we
3216-
// commit to DC being the canonical definition now, and will fix this when
3217-
// we load the update record.
3218-
if (!DD) {
3219-
DD = new (Reader.getContext()) struct CXXRecordDecl::DefinitionData(RD);
3220-
RD->setCompleteDefinition(true);
3221-
RD->DefinitionData = DD;
3222-
RD->getCanonicalDecl()->DefinitionData = DD;
3223-
3224-
// Track that we did this horrible thing so that we can fix it later.
3225-
Reader.PendingFakeDefinitionData.insert(
3226-
std::make_pair(DD, ASTReader::PendingFakeDefinitionKind::Fake));
3227-
}
3228-
3229-
return DD->Definition;
3230-
}
3247+
if (auto *RD = dyn_cast<CXXRecordDecl>(DC))
3248+
return getOrFakePrimaryClassDefinition(Reader, RD);
32313249

32323250
if (auto *RD = dyn_cast<RecordDecl>(DC))
32333251
return RD->getDefinition();

0 commit comments

Comments
 (0)