Skip to content

Commit 9454716

Browse files
committed
[modules] Fix miscompilation when using two RecordDecl definitions with the same name.
When deserializing a RecordDecl we don't enforce that redeclaration chain contains only a single definition. So if the canonical decl is not a definition itself, `RecordType::getDecl` can return different objects before and after an include. It means we can build CGRecordLayout for one RecordDecl with its set of FieldDecl but try to use it with FieldDecl belonging to a different RecordDecl. With assertions enabled it results in > Assertion failed: (FieldInfo.count(FD) && "Invalid field for record!"), > function getLLVMFieldNo, file llvm-project/clang/lib/CodeGen/CGRecordLayout.h, line 199. and with assertions disabled a bunch of fields are treated as their memory is located at offset 0. Fix by keeping the first encountered RecordDecl definition and marking the subsequent ones as non-definitions. Also need to merge FieldDecl properly, so that `getPrimaryMergedDecl` works correctly and during name lookup we don't treat fields from same-name RecordDecl as ambiguous. rdar://80184238 Differential Revision: https://reviews.llvm.org/D106994 (cherry picked from commit 93764ff)
1 parent 6aa82ad commit 9454716

File tree

15 files changed

+211
-3
lines changed

15 files changed

+211
-3
lines changed

clang/include/clang/Serialization/ASTReader.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,10 @@ class ASTReader
11551155
/// definitions. Only populated when using modules in C++.
11561156
llvm::DenseMap<EnumDecl *, EnumDecl *> EnumDefinitions;
11571157

1158+
/// A mapping from canonical declarations of records to their canonical
1159+
/// definitions. Doesn't cover CXXRecordDecl.
1160+
llvm::DenseMap<RecordDecl *, RecordDecl *> RecordDefinitions;
1161+
11581162
/// When reading a Stmt tree, Stmt operands are placed in this stack.
11591163
SmallVector<Stmt *, 16> StmtStack;
11601164

clang/lib/Serialization/ASTCommon.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ bool serialization::needsAnonymousDeclarationNumber(const NamedDecl *D) {
474474
// Otherwise, we only care about anonymous class members / block-scope decls.
475475
// FIXME: We need to handle lambdas and blocks within inline / templated
476476
// variables too.
477-
if (D->getDeclName() || !isa<CXXRecordDecl>(D->getLexicalDeclContext()))
477+
if (D->getDeclName() || !isa<RecordDecl>(D->getLexicalDeclContext()))
478478
return false;
479479
return isa<TagDecl>(D) || isa<FieldDecl>(D);
480480
}

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ namespace clang {
332332
RedeclarableResult VisitTagDecl(TagDecl *TD);
333333
void VisitEnumDecl(EnumDecl *ED);
334334
RedeclarableResult VisitRecordDeclImpl(RecordDecl *RD);
335-
void VisitRecordDecl(RecordDecl *RD) { VisitRecordDeclImpl(RD); }
335+
void VisitRecordDecl(RecordDecl *RD);
336336
RedeclarableResult VisitCXXRecordDeclImpl(CXXRecordDecl *D);
337337
void VisitCXXRecordDecl(CXXRecordDecl *D) { VisitCXXRecordDeclImpl(D); }
338338
RedeclarableResult VisitClassTemplateSpecializationDeclImpl(
@@ -808,6 +808,34 @@ ASTDeclReader::VisitRecordDeclImpl(RecordDecl *RD) {
808808
return Redecl;
809809
}
810810

811+
void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) {
812+
VisitRecordDeclImpl(RD);
813+
814+
// Maintain the invariant of a redeclaration chain containing only
815+
// a single definition.
816+
if (RD->isCompleteDefinition()) {
817+
RecordDecl *Canon = static_cast<RecordDecl *>(RD->getCanonicalDecl());
818+
RecordDecl *&OldDef = Reader.RecordDefinitions[Canon];
819+
if (!OldDef) {
820+
// This is the first time we've seen an imported definition. Look for a
821+
// local definition before deciding that we are the first definition.
822+
for (auto *D : merged_redecls(Canon)) {
823+
if (!D->isFromASTFile() && D->isCompleteDefinition()) {
824+
OldDef = D;
825+
break;
826+
}
827+
}
828+
}
829+
if (OldDef) {
830+
Reader.MergedDeclContexts.insert(std::make_pair(RD, OldDef));
831+
RD->setCompleteDefinition(false);
832+
Reader.mergeDefinitionVisibility(OldDef, RD);
833+
} else {
834+
OldDef = RD;
835+
}
836+
}
837+
}
838+
811839
void ASTDeclReader::VisitValueDecl(ValueDecl *VD) {
812840
VisitNamedDecl(VD);
813841
// For function declarations, defer reading the type in case the function has
@@ -2647,7 +2675,7 @@ static bool allowODRLikeMergeInC(NamedDecl *ND) {
26472675
if (!ND)
26482676
return false;
26492677
// TODO: implement merge for other necessary decls.
2650-
if (isa<EnumConstantDecl>(ND))
2678+
if (isa<EnumConstantDecl, FieldDecl, IndirectFieldDecl>(ND))
26512679
return true;
26522680
return false;
26532681
}
@@ -3319,6 +3347,9 @@ DeclContext *ASTDeclReader::getPrimaryContextForMerging(ASTReader &Reader,
33193347
return DD->Definition;
33203348
}
33213349

3350+
if (auto *RD = dyn_cast<RecordDecl>(DC))
3351+
return RD->getDefinition();
3352+
33223353
if (auto *ED = dyn_cast<EnumDecl>(DC))
33233354
return ED->getASTContext().getLangOpts().CPlusPlus? ED->getDefinition()
33243355
: nullptr;
@@ -3402,6 +3433,9 @@ ASTDeclReader::getPrimaryDCForAnonymousDecl(DeclContext *LexicalDC) {
34023433
if (auto *MD = dyn_cast<ObjCMethodDecl>(D))
34033434
if (MD->isThisDeclarationADefinition())
34043435
return MD;
3436+
if (auto *RD = dyn_cast<RecordDecl>(D))
3437+
if (RD->isThisDeclarationADefinition())
3438+
return RD;
34053439
}
34063440

34073441
// No merged definition yet.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// It is important to have a definition *after* non-definition declaration.
2+
typedef struct _Buffer Buffer;
3+
struct _Buffer {
4+
int a;
5+
int b;
6+
int c;
7+
};
8+
9+
typedef struct _AnonymousStruct AnonymousStruct;
10+
struct _AnonymousStruct {
11+
struct {
12+
int x;
13+
int y;
14+
};
15+
};
16+
17+
typedef union _UnionRecord UnionRecord;
18+
union _UnionRecord {
19+
int u: 2;
20+
int v: 4;
21+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
framework module RecordDef {
2+
header "RecordDef.h"
3+
export *
4+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// It is important to have a definition *after* non-definition declaration.
2+
typedef struct _Buffer Buffer;
3+
struct _Buffer {
4+
int a;
5+
int b;
6+
int c;
7+
};
8+
9+
typedef struct _AnonymousStruct AnonymousStruct;
10+
struct _AnonymousStruct {
11+
struct {
12+
int x;
13+
int y;
14+
};
15+
};
16+
17+
typedef union _UnionRecord UnionRecord;
18+
union _UnionRecord {
19+
int u: 2;
20+
int v: 4;
21+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
framework module RecordDefCopy {
2+
header "RecordDefCopy.h"
3+
export *
4+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// It is important to have a definition *after* non-definition declaration.
2+
typedef struct _Buffer Buffer;
3+
struct _Buffer {
4+
int a;
5+
int b;
6+
int c;
7+
};
8+
9+
typedef struct _AnonymousStruct AnonymousStruct;
10+
struct _AnonymousStruct {
11+
struct {
12+
int x;
13+
int y;
14+
};
15+
};
16+
17+
typedef union _UnionRecord UnionRecord;
18+
union _UnionRecord {
19+
int u: 2;
20+
int v: 4;
21+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Empty header to create a module.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
framework module RecordDefHidden {
2+
header "Visible.h"
3+
export *
4+
5+
explicit module Hidden {
6+
header "Hidden.h"
7+
export *
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#import <RecordDef/RecordDef.h>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
framework module RecordDefIncluder {
2+
header "RecordDefIncluder.h"
3+
export *
4+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir %t
3+
// RUN: %clang_cc1 -emit-llvm -o %t/test.bc -F%S/Inputs/merge-record-definition %s \
4+
// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache -fmodule-name=RecordDef
5+
// RUN: %clang_cc1 -emit-llvm -o %t/test.bc -F%S/Inputs/merge-record-definition %s -DMODULAR_BEFORE_TEXTUAL \
6+
// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache -fmodule-name=RecordDef
7+
8+
// Test a case when a struct definition once is included from a textual header and once from a module.
9+
10+
#ifdef MODULAR_BEFORE_TEXTUAL
11+
#import <RecordDefIncluder/RecordDefIncluder.h>
12+
#else
13+
#import <RecordDef/RecordDef.h>
14+
#endif
15+
16+
void bibi(void) {
17+
Buffer buf;
18+
buf.b = 1;
19+
AnonymousStruct strct;
20+
strct.x = 1;
21+
UnionRecord rec;
22+
rec.u = 1;
23+
}
24+
25+
#ifdef MODULAR_BEFORE_TEXTUAL
26+
#import <RecordDef/RecordDef.h>
27+
#else
28+
#import <RecordDefIncluder/RecordDefIncluder.h>
29+
#endif
30+
31+
void mbap(void) {
32+
Buffer buf;
33+
buf.c = 2;
34+
AnonymousStruct strct;
35+
strct.y = 2;
36+
UnionRecord rec;
37+
rec.v = 2;
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir %t
3+
// RUN: %clang_cc1 -emit-llvm -o %t/test.bc -F%S/Inputs/merge-record-definition %s \
4+
// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache
5+
6+
// Test a case when a struct definition is first imported as invisible and then as visible.
7+
8+
#import <RecordDefHidden/Visible.h>
9+
#import <RecordDef/RecordDef.h>
10+
11+
void bibi(void) {
12+
Buffer buf;
13+
buf.b = 1;
14+
AnonymousStruct strct;
15+
strct.y = 1;
16+
UnionRecord rec;
17+
rec.u = 1;
18+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir %t
3+
// RUN: %clang_cc1 -emit-llvm -o %t/test.bc -F%S/Inputs/merge-record-definition %s \
4+
// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache
5+
6+
// Test a case when a struct definition is present in two different modules.
7+
8+
#import <RecordDef/RecordDef.h>
9+
10+
void bibi(void) {
11+
Buffer buf;
12+
buf.b = 1;
13+
AnonymousStruct strct;
14+
strct.x = 1;
15+
UnionRecord rec;
16+
rec.u = 1;
17+
}
18+
19+
#import <RecordDefCopy/RecordDefCopy.h>
20+
21+
void mbap(void) {
22+
Buffer buf;
23+
buf.c = 2;
24+
AnonymousStruct strct;
25+
strct.y = 2;
26+
UnionRecord rec;
27+
rec.v = 2;
28+
}

0 commit comments

Comments
 (0)