Skip to content

Commit 7200e10

Browse files
author
Greg Parker
authored
[runtime] Add tests of pre-stable ABI objects with the stable ABI runtime (#21403)
* [test] Fix objc_old_swift when objc/objc-internal.h is not in the SDK.
1 parent f1bce6d commit 7200e10

File tree

4 files changed

+259
-0
lines changed

4 files changed

+259
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include <objc/objc.h>
2+
OBJC_EXTERN bool CanTestOldABI();
3+
OBJC_EXTERN id _Nonnull AllocOldABIObject();
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#include "OldABI.h"
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include <limits.h>
6+
#include <unistd.h>
7+
#include <sys/mman.h>
8+
#include <malloc/malloc.h>
9+
#include <objc/runtime.h>
10+
#include <Foundation/Foundation.h>
11+
12+
// Implementation of ObjC classes
13+
// with bits set to mimic the pre-stable Swift ABI
14+
// and additional memory protection to detect mis-use
15+
16+
#if __has_include(<objc/objc-internal.h>)
17+
#include <objc/objc-internal.h>
18+
#else
19+
extern "C" Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class metacls);
20+
extern "C" id _objc_rootRetain(id);
21+
extern "C" void _objc_rootRelease(id);
22+
extern "C" id _objc_rootAutorelease(id);
23+
extern "C" uintptr_t _objc_rootRetainCount(id);
24+
extern "C" bool _objc_rootTryRetain(id);
25+
extern "C" bool _objc_rootIsDeallocating(id);
26+
#endif
27+
28+
// This class stands in for the pre-stable ABI's SwiftObject.
29+
// Stable Swift's class is named Swift._SwiftObject (but mangled).
30+
31+
__attribute__((objc_root_class))
32+
@interface SwiftObject { id isa; } @end
33+
34+
@implementation SwiftObject
35+
+(void)initialize { }
36+
+(id)allocWithZone:(struct _malloc_zone_t *)zone {
37+
return class_createInstance(self, 0);
38+
}
39+
+(id)alloc { return [self allocWithZone:nil]; }
40+
+(id)class { return self; }
41+
-(id)class { return object_getClass(self); }
42+
+(id)superclass { return class_getSuperclass(self); }
43+
-(id)superclass { return class_getSuperclass([self class]); }
44+
+(BOOL)isMemberOfClass:(Class)cls { return object_getClass(self) == cls; }
45+
-(BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }
46+
-(id)self { return self; }
47+
-(BOOL)isProxy { return NO; }
48+
-(struct _malloc_zone_t *)zone { return malloc_default_zone(); }
49+
-(void)doesNotRecognizeSelector:(SEL)sel {
50+
Class cls = [self class];
51+
fprintf(stderr, "unrecognized selector %c[%s %s]\n",
52+
class_isMetaClass(cls) ? '+' : '-',
53+
class_getName(cls), sel_getName(sel));
54+
abort();
55+
}
56+
57+
+(id)retain { return self; }
58+
+(void)release { }
59+
+(id)autorelease { return self; }
60+
+(uintptr_t)retainCount { return ~(uintptr_t)0; }
61+
+(BOOL)_tryRetain { return YES; }
62+
+(BOOL)_isDeallocating { return NO; }
63+
64+
-(id)retain { return _objc_rootRetain(self); }
65+
-(void)release { _objc_rootRelease(self); }
66+
-(id)autorelease { return _objc_rootAutorelease(self); }
67+
-(uintptr_t)retainCount { return _objc_rootRetainCount(self); }
68+
-(BOOL)_tryRetain { return _objc_rootTryRetain(self); }
69+
-(BOOL)_isDeallocating { return _objc_rootIsDeallocating(self); }
70+
-(void)dealloc { object_dispose(self); }
71+
72+
-(BOOL)isKindOfClass:(Class)other {
73+
for (Class cls = object_getClass(self); cls; cls = class_getSuperclass(cls))
74+
if (cls == other) return YES;
75+
return NO;
76+
}
77+
+(BOOL)isSubclassOfClass:(Class)other {
78+
for (Class cls = self; cls; cls = class_getSuperclass(cls))
79+
if (cls == other) return YES;
80+
return NO;
81+
}
82+
-(BOOL)respondsToSelector:(SEL)sel {
83+
if (!sel) return NO;
84+
return class_respondsToSelector(object_getClass(self), sel);
85+
}
86+
+(BOOL)instancesRespondToSelector:(SEL)sel {
87+
if (!sel) return NO;
88+
return class_respondsToSelector(self, sel);
89+
}
90+
91+
-(uintptr_t)hash { return (uintptr_t)self; }
92+
-(BOOL)isEqual:(id)other { return self == other; }
93+
+(NSString *)description { return @"FakeSwiftObject class"; }
94+
-(NSString *)description { return @"FakeSwiftObject instance"; }
95+
-(NSString *)debugDescription { return [self description]; }
96+
97+
- (BOOL)isNSArray__ { return NO; }
98+
- (BOOL)isNSCFConstantString__ { return NO; }
99+
- (BOOL)isNSData__ { return NO; }
100+
- (BOOL)isNSDate__ { return NO; }
101+
- (BOOL)isNSDictionary__ { return NO; }
102+
- (BOOL)isNSObject__ { return NO; }
103+
- (BOOL)isNSOrderedSet__ { return NO; }
104+
- (BOOL)isNSNumber__ { return NO; }
105+
- (BOOL)isNSSet__ { return NO; }
106+
- (BOOL)isNSString__ { return NO; }
107+
- (BOOL)isNSTimeZone__ { return NO; }
108+
- (BOOL)isNSValue__ { return NO; }
109+
110+
@end
111+
112+
113+
static char *AllocTailGuardedPointer(size_t size) {
114+
// Round up to page boundary.
115+
size_t writeableSize = (size + PAGE_MAX_SIZE - 1) & ~(PAGE_MAX_SIZE - 1);
116+
117+
// Allocate writeable memory plus one guard page.
118+
char *writeableBuffer = (char *)mmap(0, writeableSize + PAGE_MAX_SIZE,
119+
PROT_READ | PROT_WRITE,
120+
MAP_ANON | MAP_PRIVATE, -1, 0);
121+
if (writeableBuffer == MAP_FAILED) abort();
122+
123+
// Mark the guard page inaccessible.
124+
mprotect(writeableBuffer + writeableSize, PAGE_MAX_SIZE, 0);
125+
126+
// Scribble on the prefix.
127+
memset(writeableBuffer, 0x55, writeableSize - size);
128+
129+
// Return the address just before the guard page.
130+
// FIXME: this doesn't handle alignment properly,
131+
// but we don't need to for this test's usage.
132+
return writeableBuffer + writeableSize - size;
133+
}
134+
135+
static Class CreateOldABISubclass(Class supercls, const char *name) {
136+
// Allocate class and metaclass in tail-guarded memory.
137+
// If the Swift runtime incorrectly tries to read Swift
138+
// metadata from this class then it'll crash.
139+
char *clsbuf = AllocTailGuardedPointer(5*sizeof(uintptr_t));
140+
char *metabuf = AllocTailGuardedPointer(5*sizeof(uintptr_t));
141+
Class result = objc_initializeClassPair(supercls, name,
142+
(Class)clsbuf, (Class)metabuf);
143+
144+
// Set the old is-Swift bit in the class.
145+
uintptr_t *words = (uintptr_t *)clsbuf;
146+
words[4] |= 1;
147+
148+
return result;
149+
}
150+
151+
static Class FakeOldABIClass;
152+
153+
__attribute__((constructor))
154+
static void initialize(void) {
155+
FakeOldABIClass = CreateOldABISubclass([SwiftObject class],
156+
"_TtC6OldABI8Subclass");
157+
}
158+
159+
bool CanTestOldABI() {
160+
// These tests don't work until the stable ABI is using its designed bit.
161+
// This check can be removed after SWIFT_DARWIN_ENABLE_STABLE_ABI_BIT
162+
// is set everywhere.
163+
Class cls = objc_getClass("_TtCs19__EmptyArrayStorage");
164+
if (!cls) abort();
165+
uintptr_t *words = (uintptr_t *)cls;
166+
if ((words[4] & 3) != 2) return false; // wrong stable is-Swift bit
167+
return true;
168+
}
169+
170+
id AllocOldABIObject() {
171+
return [FakeOldABIClass alloc];
172+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module OldABI {
2+
header "OldABI.h"
3+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// RUN: %empty-directory(%t)
2+
//
3+
// RUN: cp %s %t/main.swift
4+
// RUN: %target-clang %S/Inputs/OldABI/OldABI.mm -g -c -o %t/OldABI.o
5+
// RUN: %target-build-swift %t/main.swift -framework Foundation -I %S/Inputs/OldABI/ -Xlinker %t/OldABI.o -o %t/objc_old_swift -Xfrontend -disable-access-control
6+
// RUN: %target-codesign %t/objc_old_swift
7+
// RUN: %target-run %t/objc_old_swift
8+
9+
// REQUIRES: executable_test
10+
// REQUIRES: objc_interop
11+
12+
// Verify that objects that appear to be from the pre-stable Swift ABI
13+
// are correctly ignored by stable Swift's entry points.
14+
15+
import Foundation
16+
import OldABI
17+
import StdlibUnittest
18+
19+
var tests = TestSuite("objc_old_swift")
20+
21+
tests.test("description")
22+
.skip(.custom({ !CanTestOldABI() },
23+
reason: "not using stable ABI's is-Swift bit yet"))
24+
.code {
25+
let obj = AllocOldABIObject()
26+
expectEqual(String(describing:obj), "OldABI.Subclass")
27+
expectEqual((obj as AnyObject).description!, "FakeSwiftObject instance")
28+
}
29+
30+
tests.test("casts")
31+
.skip(.custom({ !CanTestOldABI() },
32+
reason: "not using stable ABI's is-Swift bit yet"))
33+
.code {
34+
let obj = AllocOldABIObject()
35+
expectNil(obj as? String)
36+
expectNotNil(obj as Any)
37+
expectNotNil(obj as AnyObject)
38+
}
39+
40+
tests.test("array")
41+
.skip(.custom({ !CanTestOldABI() },
42+
reason: "not using stable ABI's is-Swift bit yet"))
43+
.code {
44+
let array = Array(repeating: AllocOldABIObject(), count:5)
45+
expectEqual(String(describing: array), "[OldABI.Subclass, OldABI.Subclass, OldABI.Subclass, OldABI.Subclass, OldABI.Subclass]")
46+
47+
var array2 = Array(repeating: AllocOldABIObject(), count:0)
48+
for i in 0..<array.count {
49+
expectNotNil(array[i])
50+
array2.append(i as NSNumber)
51+
array2.append(array[i]);
52+
}
53+
expectEqual(String(describing: array2), "[0, OldABI.Subclass, 1, OldABI.Subclass, 2, OldABI.Subclass, 3, OldABI.Subclass, 4, OldABI.Subclass]")
54+
55+
// Bridge an array of pre-stable objects to NSArray
56+
let nsarray = NSMutableArray(array: array2)
57+
expectEqual(nsarray.description, #"""
58+
(
59+
0,
60+
"FakeSwiftObject instance",
61+
1,
62+
"FakeSwiftObject instance",
63+
2,
64+
"FakeSwiftObject instance",
65+
3,
66+
"FakeSwiftObject instance",
67+
4,
68+
"FakeSwiftObject instance"
69+
)
70+
"""#)
71+
72+
nsarray.add(5 as NSNumber)
73+
74+
// Bridge back from NSArray
75+
let array3 = nsarray as [AnyObject]
76+
expectEqual(String(describing: array3), "[0, OldABI.Subclass, 1, OldABI.Subclass, 2, OldABI.Subclass, 3, OldABI.Subclass, 4, OldABI.Subclass, 5]")
77+
}
78+
79+
// FIXME: add coverage of more Swift runtime entrypoints
80+
81+
runAllTests()

0 commit comments

Comments
 (0)