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.
This commit is contained in:
Richard Smith
2023-07-24 17:34:08 -07:00
parent 4af01bf956
commit 61c7a9140b
2 changed files with 46 additions and 24 deletions

View File

@@ -98,6 +98,10 @@ Improvements to Clang's diagnostics
Bug Fixes in This Version
-------------------------
- Fixed an issue where a class template specialization whose declaration is
instantiated in one module and whose definition is instantiated in another
module may end up with members associated with the wrong declaration of the
class, which can result in miscompiles in some cases.
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -181,6 +181,13 @@ namespace clang {
static void setAnonymousDeclForMerging(ASTReader &Reader, DeclContext *DC,
unsigned Index, NamedDecl *D);
/// Commit to a primary definition of the class RD, which is known to be
/// a definition of the class. We might not have read the definition data
/// for it yet. If we haven't then allocate placeholder definition data
/// now too.
static CXXRecordDecl *getOrFakePrimaryClassDefinition(ASTReader &Reader,
CXXRecordDecl *RD);
/// Results from loading a RedeclarableDecl.
class RedeclarableResult {
Decl *MergeWith;
@@ -598,7 +605,13 @@ void ASTDeclReader::VisitDecl(Decl *D) {
auto *LexicalDC = readDeclAs<DeclContext>();
if (!LexicalDC)
LexicalDC = SemaDC;
DeclContext *MergedSemaDC = Reader.MergedDeclContexts.lookup(SemaDC);
// If the context is a class, we might not have actually merged it yet, in
// the case where the definition comes from an update record.
DeclContext *MergedSemaDC;
if (auto *RD = dyn_cast<CXXRecordDecl>(SemaDC))
MergedSemaDC = getOrFakePrimaryClassDefinition(Reader, RD);
else
MergedSemaDC = Reader.MergedDeclContexts.lookup(SemaDC);
// Avoid calling setLexicalDeclContext() directly because it uses
// Decl::getASTContext() internally which is unsafe during derialization.
D->setDeclContextsImpl(MergedSemaDC ? MergedSemaDC : SemaDC, LexicalDC,
@@ -3198,6 +3211,32 @@ uint64_t ASTReader::getGlobalBitOffset(ModuleFile &M, uint64_t LocalOffset) {
return LocalOffset + M.GlobalBitOffset;
}
CXXRecordDecl *
ASTDeclReader::getOrFakePrimaryClassDefinition(ASTReader &Reader,
CXXRecordDecl *RD) {
// Try to dig out the definition.
auto *DD = RD->DefinitionData;
if (!DD)
DD = RD->getCanonicalDecl()->DefinitionData;
// If there's no definition yet, then DC's definition is added by an update
// record, but we've not yet loaded that update record. In this case, we
// commit to DC being the canonical definition now, and will fix this when
// we load the update record.
if (!DD) {
DD = new (Reader.getContext()) struct CXXRecordDecl::DefinitionData(RD);
RD->setCompleteDefinition(true);
RD->DefinitionData = DD;
RD->getCanonicalDecl()->DefinitionData = DD;
// Track that we did this horrible thing so that we can fix it later.
Reader.PendingFakeDefinitionData.insert(
std::make_pair(DD, ASTReader::PendingFakeDefinitionKind::Fake));
}
return DD->Definition;
}
/// Find the context in which we should search for previous declarations when
/// looking for declarations to merge.
DeclContext *ASTDeclReader::getPrimaryContextForMerging(ASTReader &Reader,
@@ -3205,29 +3244,8 @@ DeclContext *ASTDeclReader::getPrimaryContextForMerging(ASTReader &Reader,
if (auto *ND = dyn_cast<NamespaceDecl>(DC))
return ND->getOriginalNamespace();
if (auto *RD = dyn_cast<CXXRecordDecl>(DC)) {
// Try to dig out the definition.
auto *DD = RD->DefinitionData;
if (!DD)
DD = RD->getCanonicalDecl()->DefinitionData;
// If there's no definition yet, then DC's definition is added by an update
// record, but we've not yet loaded that update record. In this case, we
// commit to DC being the canonical definition now, and will fix this when
// we load the update record.
if (!DD) {
DD = new (Reader.getContext()) struct CXXRecordDecl::DefinitionData(RD);
RD->setCompleteDefinition(true);
RD->DefinitionData = DD;
RD->getCanonicalDecl()->DefinitionData = DD;
// Track that we did this horrible thing so that we can fix it later.
Reader.PendingFakeDefinitionData.insert(
std::make_pair(DD, ASTReader::PendingFakeDefinitionKind::Fake));
}
return DD->Definition;
}
if (auto *RD = dyn_cast<CXXRecordDecl>(DC))
return getOrFakePrimaryClassDefinition(Reader, RD);
if (auto *RD = dyn_cast<RecordDecl>(DC))
return RD->getDefinition();