Skip to content

[runtime] Add tests of pre-stable ABI objects with the stable ABI runtime #21403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions test/Interpreter/SDK/Inputs/OldABI/OldABI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <objc/objc.h>
OBJC_EXTERN bool CanTestOldABI();
OBJC_EXTERN id _Nonnull AllocOldABIObject();
172 changes: 172 additions & 0 deletions test/Interpreter/SDK/Inputs/OldABI/OldABI.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#include "OldABI.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc/malloc.h>
#include <objc/runtime.h>
#include <Foundation/Foundation.h>

// Implementation of ObjC classes
// with bits set to mimic the pre-stable Swift ABI
// and additional memory protection to detect mis-use

#if __has_include(<objc/objc-internal.h>)
#include <objc/objc-internal.h>
#else
extern "C" Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class metacls);
extern "C" id _objc_rootRetain(id);
extern "C" void _objc_rootRelease(id);
extern "C" id _objc_rootAutorelease(id);
extern "C" uintptr_t _objc_rootRetainCount(id);
extern "C" bool _objc_rootTryRetain(id);
extern "C" bool _objc_rootIsDeallocating(id);
#endif

// This class stands in for the pre-stable ABI's SwiftObject.
// Stable Swift's class is named Swift._SwiftObject (but mangled).

__attribute__((objc_root_class))
@interface SwiftObject { id isa; } @end

@implementation SwiftObject
+(void)initialize { }
+(id)allocWithZone:(struct _malloc_zone_t *)zone {
return class_createInstance(self, 0);
}
+(id)alloc { return [self allocWithZone:nil]; }
+(id)class { return self; }
-(id)class { return object_getClass(self); }
+(id)superclass { return class_getSuperclass(self); }
-(id)superclass { return class_getSuperclass([self class]); }
+(BOOL)isMemberOfClass:(Class)cls { return object_getClass(self) == cls; }
-(BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }
-(id)self { return self; }
-(BOOL)isProxy { return NO; }
-(struct _malloc_zone_t *)zone { return malloc_default_zone(); }
-(void)doesNotRecognizeSelector:(SEL)sel {
Class cls = [self class];
fprintf(stderr, "unrecognized selector %c[%s %s]\n",
class_isMetaClass(cls) ? '+' : '-',
class_getName(cls), sel_getName(sel));
abort();
}

+(id)retain { return self; }
+(void)release { }
+(id)autorelease { return self; }
+(uintptr_t)retainCount { return ~(uintptr_t)0; }
+(BOOL)_tryRetain { return YES; }
+(BOOL)_isDeallocating { return NO; }

-(id)retain { return _objc_rootRetain(self); }
-(void)release { _objc_rootRelease(self); }
-(id)autorelease { return _objc_rootAutorelease(self); }
-(uintptr_t)retainCount { return _objc_rootRetainCount(self); }
-(BOOL)_tryRetain { return _objc_rootTryRetain(self); }
-(BOOL)_isDeallocating { return _objc_rootIsDeallocating(self); }
-(void)dealloc { object_dispose(self); }

-(BOOL)isKindOfClass:(Class)other {
for (Class cls = object_getClass(self); cls; cls = class_getSuperclass(cls))
if (cls == other) return YES;
return NO;
}
+(BOOL)isSubclassOfClass:(Class)other {
for (Class cls = self; cls; cls = class_getSuperclass(cls))
if (cls == other) return YES;
return NO;
}
-(BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector(object_getClass(self), sel);
}
+(BOOL)instancesRespondToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector(self, sel);
}

-(uintptr_t)hash { return (uintptr_t)self; }
-(BOOL)isEqual:(id)other { return self == other; }
+(NSString *)description { return @"FakeSwiftObject class"; }
-(NSString *)description { return @"FakeSwiftObject instance"; }
-(NSString *)debugDescription { return [self description]; }

- (BOOL)isNSArray__ { return NO; }
- (BOOL)isNSCFConstantString__ { return NO; }
- (BOOL)isNSData__ { return NO; }
- (BOOL)isNSDate__ { return NO; }
- (BOOL)isNSDictionary__ { return NO; }
- (BOOL)isNSObject__ { return NO; }
- (BOOL)isNSOrderedSet__ { return NO; }
- (BOOL)isNSNumber__ { return NO; }
- (BOOL)isNSSet__ { return NO; }
- (BOOL)isNSString__ { return NO; }
- (BOOL)isNSTimeZone__ { return NO; }
- (BOOL)isNSValue__ { return NO; }

@end


static char *AllocTailGuardedPointer(size_t size) {
// Round up to page boundary.
size_t writeableSize = (size + PAGE_MAX_SIZE - 1) & ~(PAGE_MAX_SIZE - 1);

// Allocate writeable memory plus one guard page.
char *writeableBuffer = (char *)mmap(0, writeableSize + PAGE_MAX_SIZE,
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0);
if (writeableBuffer == MAP_FAILED) abort();

// Mark the guard page inaccessible.
mprotect(writeableBuffer + writeableSize, PAGE_MAX_SIZE, 0);

// Scribble on the prefix.
memset(writeableBuffer, 0x55, writeableSize - size);

// Return the address just before the guard page.
// FIXME: this doesn't handle alignment properly,
// but we don't need to for this test's usage.
return writeableBuffer + writeableSize - size;
}

static Class CreateOldABISubclass(Class supercls, const char *name) {
// Allocate class and metaclass in tail-guarded memory.
// If the Swift runtime incorrectly tries to read Swift
// metadata from this class then it'll crash.
char *clsbuf = AllocTailGuardedPointer(5*sizeof(uintptr_t));
char *metabuf = AllocTailGuardedPointer(5*sizeof(uintptr_t));
Class result = objc_initializeClassPair(supercls, name,
(Class)clsbuf, (Class)metabuf);

// Set the old is-Swift bit in the class.
uintptr_t *words = (uintptr_t *)clsbuf;
words[4] |= 1;

return result;
}

static Class FakeOldABIClass;

__attribute__((constructor))
static void initialize(void) {
FakeOldABIClass = CreateOldABISubclass([SwiftObject class],
"_TtC6OldABI8Subclass");
}

bool CanTestOldABI() {
// These tests don't work until the stable ABI is using its designed bit.
// This check can be removed after SWIFT_DARWIN_ENABLE_STABLE_ABI_BIT
// is set everywhere.
Class cls = objc_getClass("_TtCs19__EmptyArrayStorage");
if (!cls) abort();
uintptr_t *words = (uintptr_t *)cls;
if ((words[4] & 3) != 2) return false; // wrong stable is-Swift bit
return true;
}

id AllocOldABIObject() {
return [FakeOldABIClass alloc];
}
3 changes: 3 additions & 0 deletions test/Interpreter/SDK/Inputs/OldABI/module.map
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module OldABI {
header "OldABI.h"
}
81 changes: 81 additions & 0 deletions test/Interpreter/SDK/objc_old_swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// RUN: %empty-directory(%t)
//
// RUN: cp %s %t/main.swift
// RUN: %target-clang %S/Inputs/OldABI/OldABI.mm -g -c -o %t/OldABI.o
// 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
// RUN: %target-codesign %t/objc_old_swift
// RUN: %target-run %t/objc_old_swift

// REQUIRES: executable_test
// REQUIRES: objc_interop

// Verify that objects that appear to be from the pre-stable Swift ABI
// are correctly ignored by stable Swift's entry points.

import Foundation
import OldABI
import StdlibUnittest

var tests = TestSuite("objc_old_swift")

tests.test("description")
.skip(.custom({ !CanTestOldABI() },
reason: "not using stable ABI's is-Swift bit yet"))
.code {
let obj = AllocOldABIObject()
expectEqual(String(describing:obj), "OldABI.Subclass")
expectEqual((obj as AnyObject).description!, "FakeSwiftObject instance")
}

tests.test("casts")
.skip(.custom({ !CanTestOldABI() },
reason: "not using stable ABI's is-Swift bit yet"))
.code {
let obj = AllocOldABIObject()
expectNil(obj as? String)
expectNotNil(obj as Any)
expectNotNil(obj as AnyObject)
}

tests.test("array")
.skip(.custom({ !CanTestOldABI() },
reason: "not using stable ABI's is-Swift bit yet"))
.code {
let array = Array(repeating: AllocOldABIObject(), count:5)
expectEqual(String(describing: array), "[OldABI.Subclass, OldABI.Subclass, OldABI.Subclass, OldABI.Subclass, OldABI.Subclass]")

var array2 = Array(repeating: AllocOldABIObject(), count:0)
for i in 0..<array.count {
expectNotNil(array[i])
array2.append(i as NSNumber)
array2.append(array[i]);
}
expectEqual(String(describing: array2), "[0, OldABI.Subclass, 1, OldABI.Subclass, 2, OldABI.Subclass, 3, OldABI.Subclass, 4, OldABI.Subclass]")

// Bridge an array of pre-stable objects to NSArray
let nsarray = NSMutableArray(array: array2)
expectEqual(nsarray.description, #"""
(
0,
"FakeSwiftObject instance",
1,
"FakeSwiftObject instance",
2,
"FakeSwiftObject instance",
3,
"FakeSwiftObject instance",
4,
"FakeSwiftObject instance"
)
"""#)

nsarray.add(5 as NSNumber)

// Bridge back from NSArray
let array3 = nsarray as [AnyObject]
expectEqual(String(describing: array3), "[0, OldABI.Subclass, 1, OldABI.Subclass, 2, OldABI.Subclass, 3, OldABI.Subclass, 4, OldABI.Subclass, 5]")
}

// FIXME: add coverage of more Swift runtime entrypoints

runAllTests()