Skip to content

Commit 34d2cd9

Browse files
committed
[modules] Fix IRGen assertion on accessing ObjC ivar inside a method.
When have ObjCInterfaceDecl with the same name in 2 different modules, hitting the assertion > Assertion failed: (Index < RL->getFieldCount() && "Ivar is not inside record layout!"), > function lookupFieldBitOffset, file llvm-project/clang/lib/AST/RecordLayoutBuilder.cpp, line 3434. on accessing an ivar inside a method. The assertion happens because ivar belongs to one module while its containing interface belongs to another module and then we fail to find the ivar inside the containing interface. We already keep a single ObjCInterfaceDecl definition in redecleration chain and in this case containing interface was correct. The issue is with ObjCIvarDecl. IVar decl for IRGen is taken from ObjCIvarRefExpr that is created in `Sema::BuildIvarRefExpr` using ivar decl returned from `Sema::LookupIvarInObjCMethod`. And ivar lookup returns a wrong decl because basically we take the first ObjCIvarDecl found in `ASTReader::FindExternalVisibleDeclsByName` (called by `DeclContext::lookup`). And in `ASTReader.Lookups` lookup table for a wrong module comes first because `ASTReader::finishPendingActions` processes `PendingUpdateRecords` in reverse order and the first encountered ObjCIvarDecl will end up the last in `ASTReader.Lookups`. Fix by merging ObjCIvarDecl from different modules correctly and by using a canonical one in IRGen. rdar://82854574 Differential Revision: https://reviews.llvm.org/D110280 (cherry picked from commit 9fad9de)
1 parent ff20ea9 commit 34d2cd9

File tree

4 files changed

+117
-1
lines changed

4 files changed

+117
-1
lines changed

clang/include/clang/AST/DeclObjC.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,13 @@ class ObjCIvarDecl : public FieldDecl {
19651965
const ObjCIvarDecl *getNextIvar() const { return NextIvar; }
19661966
void setNextIvar(ObjCIvarDecl *ivar) { NextIvar = ivar; }
19671967

1968+
ObjCIvarDecl *getCanonicalDecl() override {
1969+
return cast<ObjCIvarDecl>(FieldDecl::getCanonicalDecl());
1970+
}
1971+
const ObjCIvarDecl *getCanonicalDecl() const {
1972+
return const_cast<ObjCIvarDecl *>(this)->getCanonicalDecl();
1973+
}
1974+
19681975
void setAccessControl(AccessControl ac) { DeclAccess = ac; }
19691976

19701977
AccessControl getAccessControl() const { return AccessControl(DeclAccess); }

clang/lib/AST/RecordLayoutBuilder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3383,6 +3383,7 @@ uint64_t ASTContext::getFieldOffset(const ValueDecl *VD) const {
33833383
uint64_t ASTContext::lookupFieldBitOffset(const ObjCInterfaceDecl *OID,
33843384
const ObjCImplementationDecl *ID,
33853385
const ObjCIvarDecl *Ivar) const {
3386+
Ivar = Ivar->getCanonicalDecl();
33863387
const ObjCInterfaceDecl *Container = Ivar->getContainingInterface();
33873388

33883389
// FIXME: We should eliminate the need to have ObjCImplementationDecl passed

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2647,7 +2647,7 @@ static bool allowODRLikeMergeInC(NamedDecl *ND) {
26472647
if (!ND)
26482648
return false;
26492649
// TODO: implement merge for other necessary decls.
2650-
if (isa<EnumConstantDecl>(ND))
2650+
if (isa<EnumConstantDecl, FieldDecl, IndirectFieldDecl>(ND))
26512651
return true;
26522652
return false;
26532653
}
@@ -3323,6 +3323,9 @@ DeclContext *ASTDeclReader::getPrimaryContextForMerging(ASTReader &Reader,
33233323
return ED->getASTContext().getLangOpts().CPlusPlus? ED->getDefinition()
33243324
: nullptr;
33253325

3326+
if (auto *OID = dyn_cast<ObjCInterfaceDecl>(DC))
3327+
return OID->getDefinition();
3328+
33263329
// We can see the TU here only if we have no Sema object. In that case,
33273330
// there's no TU scope to look in, so using the DC alone is sufficient.
33283331
if (auto *TU = dyn_cast<TranslationUnitDecl>(DC))
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
// RUN: %clang_cc1 -emit-llvm -o %t/test.bc -F%t/Frameworks %t/test.m \
4+
// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache
5+
// RUN: %clang_cc1 -emit-llvm -o %t/test.bc -F%t/Frameworks %t/test-functions.m \
6+
// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache
7+
8+
// Test a case when Objective-C interface ivars are present in two different modules.
9+
10+
//--- Frameworks/Foundation.framework/Headers/Foundation.h
11+
@interface NSObject
12+
@end
13+
14+
//--- Frameworks/Foundation.framework/Modules/module.modulemap
15+
framework module Foundation {
16+
header "Foundation.h"
17+
export *
18+
}
19+
20+
//--- Frameworks/ObjCInterface.framework/Headers/ObjCInterface.h
21+
#import <Foundation/Foundation.h>
22+
@interface ObjCInterface : NSObject {
23+
@public
24+
id _item;
25+
}
26+
@end
27+
28+
@interface WithBitFields : NSObject {
29+
@public
30+
int x: 3;
31+
int y: 4;
32+
}
33+
@end
34+
35+
//--- Frameworks/ObjCInterface.framework/Modules/module.modulemap
36+
framework module ObjCInterface {
37+
header "ObjCInterface.h"
38+
export *
39+
}
40+
41+
//--- Frameworks/ObjCInterfaceCopy.framework/Headers/ObjCInterfaceCopy.h
42+
#import <Foundation/Foundation.h>
43+
@interface ObjCInterface : NSObject {
44+
@public
45+
id _item;
46+
}
47+
@end
48+
49+
@interface WithBitFields : NSObject {
50+
@public
51+
int x: 3;
52+
int y: 4;
53+
}
54+
@end
55+
56+
// Inlined function present only in Copy.framework to make sure it uses decls from Copy module.
57+
__attribute__((always_inline)) void inlinedIVarAccessor(ObjCInterface *obj, WithBitFields *bitFields) {
58+
obj->_item = 0;
59+
bitFields->x = 0;
60+
}
61+
62+
//--- Frameworks/ObjCInterfaceCopy.framework/Modules/module.modulemap
63+
framework module ObjCInterfaceCopy {
64+
header "ObjCInterfaceCopy.h"
65+
export *
66+
}
67+
68+
//--- test.m
69+
#import <ObjCInterface/ObjCInterface.h>
70+
#import <ObjCInterfaceCopy/ObjCInterfaceCopy.h>
71+
72+
@implementation ObjCInterface
73+
- (void)test:(id)item {
74+
_item = item;
75+
}
76+
@end
77+
78+
@implementation WithBitFields
79+
- (void)reset {
80+
x = 0;
81+
y = 0;
82+
}
83+
@end
84+
85+
//--- test-functions.m
86+
#import <ObjCInterface/ObjCInterface.h>
87+
88+
void testAccessIVar(ObjCInterface *obj, id item) {
89+
obj->_item = item;
90+
}
91+
void testAccessBitField(WithBitFields *obj) {
92+
obj->x = 0;
93+
}
94+
95+
#import <ObjCInterfaceCopy/ObjCInterfaceCopy.h>
96+
97+
void testAccessIVarLater(ObjCInterface *obj, id item) {
98+
obj->_item = item;
99+
}
100+
void testAccessBitFieldLater(WithBitFields *obj) {
101+
obj->y = 0;
102+
}
103+
void testInlinedFunction(ObjCInterface *obj, WithBitFields *bitFields) {
104+
inlinedIVarAccessor(obj, bitFields);
105+
}

0 commit comments

Comments
 (0)