Skip to content

Commit e5c06e3

Browse files
committed
Fix offset calculation for ivar bitfields.
In some cases, we were allowing ivars declared as bitfields to overlap other ivars, which caused memory corruption. This showed up in OOlite.
1 parent 37ed75a commit e5c06e3

File tree

4 files changed

+74
-7
lines changed

4 files changed

+74
-7
lines changed

Test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ endif ()
1313
set(TESTS
1414
alias.m
1515
alignTest.m
16+
bitfield.m
1617
AllocatePair.m
1718
AssociatedObject.m
1819
AssociatedObject2.m

Test/IVarSuperclassOverlap.m

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,18 @@ int main(int argc, char *argv[])
8989
assert(ivar_getOffset(c3) == baseSmallOffset + 4);
9090
assert(ivar_getOffset(b1) == baseSmallOffset + 2);
9191
assert(ivar_getOffset(b2) == baseSmallOffset + 2);
92-
assert(ivar_getOffset(b3) == baseSmallOffset + 3);
93-
assert(ivar_getOffset(b4) == baseSmallOffset + 3);
94-
assert(ivar_getOffset(notBitfield) == baseSmallOffset + 4);
92+
// These could be tighter, but the way that clang currently emits ivars for
93+
// bitfields is a bit odd. Unfortunately, it sometimes adds a small
94+
// displacement so that it can load individual words, but the metadata
95+
// doesn't tell us anything about them. Fixing this would require using an
96+
// extra metadata flag or similar to indicate that the size of the ivar is
97+
// not the storage size.
98+
assert((ivar_getOffset(b3) == baseSmallOffset + 3) ||
99+
(ivar_getOffset(b3) == baseSmallOffset + 4));
100+
assert((ivar_getOffset(b4) == baseSmallOffset + 3) ||
101+
(ivar_getOffset(b4) == baseSmallOffset + 4));
102+
assert((ivar_getOffset(notBitfield) == baseSmallOffset + 4) ||
103+
(ivar_getOffset(notBitfield) == baseSmallOffset + 6));
95104
#endif
96105

97106

Test/bitfield.m

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include "stdio.h"
2+
#include "Test.h"
3+
#include "objc/runtime.h"
4+
#include "objc/encoding.h"
5+
6+
@interface Bitfield : Test
7+
{
8+
unsigned short first;
9+
unsigned isShip: 1,
10+
isStation: 1;
11+
unsigned y;
12+
}
13+
14+
@end
15+
@implementation Bitfield
16+
- (BOOL)isShip { return isShip; }
17+
- (BOOL)isStation { return isStation; }
18+
- (void)setShip: (BOOL)aValue
19+
{
20+
isShip = aValue;
21+
}
22+
- (void)setStation: (BOOL)aValue
23+
{
24+
isStation = aValue;
25+
}
26+
- (void)setY: (int)anInt
27+
{
28+
y = anInt;
29+
}
30+
@end
31+
32+
static size_t offset(const char *ivar)
33+
{
34+
return ivar_getOffset(class_getInstanceVariable([Bitfield class], ivar));
35+
}
36+
37+
static size_t size(const char *ivar)
38+
{
39+
return objc_sizeof_type(ivar_getTypeEncoding(class_getInstanceVariable([Bitfield class], ivar)));
40+
}
41+
42+
int main(void)
43+
{
44+
Bitfield *bf = [Bitfield new];
45+
assert(![bf isShip]);
46+
assert(![bf isStation]);
47+
[bf setShip: YES];
48+
assert([bf isShip]);
49+
assert(![bf isStation]);
50+
[bf setStation: YES];
51+
[bf setY: 0];
52+
assert([bf isShip]);
53+
assert([bf isStation]);
54+
assert(offset("isShip") >= offset("first") + size("first"));
55+
assert(offset("isShip") == offset("isStation"));
56+
assert(offset("y") >= offset("isStation") + size("isStation"));
57+
}

ivar.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,25 @@ PRIVATE void objc_compute_ivar_offsets(Class class)
8585
// that contains them. If we are in a bitfield, then we need
8686
// to make sure that we don't add any displacement from the
8787
// previous value.
88-
if (*ivar->offset < last_offset + last_size)
88+
if ((i != 0) && (*ivar->offset == last_offset))
8989
{
90-
*ivar->offset = last_computed_offset + (*ivar->offset - last_offset);
91-
ivar_size = 0;
90+
*ivar->offset = last_computed_offset;
9291
continue;
9392
}
9493
last_offset = *ivar->offset;
9594
*ivar->offset = next_ivar;
96-
last_computed_offset = *ivar->offset;
9795
next_ivar += ivar_size;
9896
last_size = ivar->size;
9997
size_t align = ivarGetAlign(ivar);
98+
// If the alignment is insufficient, round it up.
10099
if ((*ivar->offset + refcount_size) % align != 0)
101100
{
102101
long padding = align - ((*ivar->offset + refcount_size) % align);
103102
*ivar->offset += padding;
104103
class->instance_size += padding;
105104
next_ivar += padding;
106105
}
106+
last_computed_offset = *ivar->offset;
107107
assert((*ivar->offset + sizeof(uintptr_t)) % ivarGetAlign(ivar) == 0);
108108
class->instance_size += ivar_size;
109109
}

0 commit comments

Comments
 (0)