Skip to content

Commit 9fad9de

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
1 parent ebcfd3a commit 9fad9de

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

clang/include/clang/AST/DeclObjC.h

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

1958+
ObjCIvarDecl *getCanonicalDecl() override {
1959+
return cast<ObjCIvarDecl>(FieldDecl::getCanonicalDecl());
1960+
}
1961+
const ObjCIvarDecl *getCanonicalDecl() const {
1962+
return const_cast<ObjCIvarDecl *>(this)->getCanonicalDecl();
1963+
}
1964+
19581965
void setAccessControl(AccessControl ac) { DeclAccess = ac; }
19591966

19601967
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
@@ -3404,6 +3404,7 @@ uint64_t ASTContext::getFieldOffset(const ValueDecl *VD) const {
34043404
uint64_t ASTContext::lookupFieldBitOffset(const ObjCInterfaceDecl *OID,
34053405
const ObjCImplementationDecl *ID,
34063406
const ObjCIvarDecl *Ivar) const {
3407+
Ivar = Ivar->getCanonicalDecl();
34073408
const ObjCInterfaceDecl *Container = Ivar->getContainingInterface();
34083409

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

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3350,6 +3350,9 @@ DeclContext *ASTDeclReader::getPrimaryContextForMerging(ASTReader &Reader,
33503350
return ED->getASTContext().getLangOpts().CPlusPlus? ED->getDefinition()
33513351
: nullptr;
33523352

3353+
if (auto *OID = dyn_cast<ObjCInterfaceDecl>(DC))
3354+
return OID->getDefinition();
3355+
33533356
// We can see the TU here only if we have no Sema object. In that case,
33543357
// there's no TU scope to look in, so using the DC alone is sufficient.
33553358
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)