Skip to content
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
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn build(b: *std.Build) !void {
});
tests.linkSystemLibrary("objc");
tests.linkFramework("Foundation");
tests.linkFramework("AppKit"); // Required by 'tagged pointer' test.
try addAppleSDK(b, tests.root_module);
b.installArtifact(tests);

Expand Down
68 changes: 67 additions & 1 deletion src/object.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ pub const Object = struct {
/// Convert a raw "id" into an Object. id must fit the size of the
/// normal C "id" type (i.e. a `usize`).
pub fn fromId(id: anytype) Object {
return .{ .value = @ptrCast(@alignCast(id)) };
if (@sizeOf(@TypeOf(id)) != @sizeOf(c.id)) {
@compileError("invalid id type");
}

// Some pointers in Objective-C are "tagged pointers", which
// may be used for small objects and literals (NSNumber, NSString).
// It's an internal implementation detail that replaces heap
// allocation with direct encoding within the pointer itself.
// This may result in UNALIGNED POINTERS!
const ptr: c.id = blk: {
@setRuntimeSafety(false);
break :blk @ptrCast(@alignCast(id));
};

return .{ .value = ptr };
}

/// Returns the class of an object.
Expand Down Expand Up @@ -150,3 +164,55 @@ test "retain object" {

obj.msgSend(void, objc.sel("dealloc"), .{});
}

test "tagged pointer" {
const testing = std.testing;

// We can't force Objective-C to provide us with an unaligned tagged
// pointer, so we try several times using different classes. We use
// different classes instead of values, since pointers from the same
// class will have the same alignment during a single execution (aarch64).
const obj = blk: {
var Class = objc.getClass("NSNumber").?;
var sel = objc.Sel.registerName("numberWithChar:");
var obj = Class.msgSend(objc.Object, sel, .{@as(u8, @intCast(5))});

// We're only interested in an unaligned pointer.
if (!std.mem.isAligned(@intFromPtr(obj.value), @alignOf(usize))) break :blk obj;

Class = objc.getClass("NSString").?;
sel = objc.Sel.registerName("stringWithUTF8String:");
obj = Class.msgSend(objc.Object, sel, .{"foo"});
if (!std.mem.isAligned(@intFromPtr(obj.value), @alignOf(usize))) break :blk obj;

Class = objc.getClass("NSDate").?;
sel = objc.Sel.registerName("date");
obj = Class.msgSend(objc.Object, sel, .{});
if (!std.mem.isAligned(@intFromPtr(obj.value), @alignOf(usize))) break :blk obj;

const colors = [_][:0]const u8{
"clearColor", "blackColor", "blueColor", "brownColor", "cyanColor",
"darkGrayColor", "grayColor", "greenColor", "lightGrayColor", "magentaColor",
"orangeColor", "purpleColor", "redColor", "whiteColor", "yellowColor",
};

Class = objc.getClass("NSColor").?;
for (colors) |color| {
sel = objc.Sel.registerName(color);
obj = Class.msgSend(objc.Object, sel, .{});
if (!std.mem.isAligned(@intFromPtr(obj.value), @alignOf(usize))) break :blk obj;
}

// In the unlikely event that we don't find an unaligned tagged pointer.
std.log.warn("skipped 'tagged pointer' test because we couldn't find an unaligned tagged pointer", .{});
return error.SkipZigTest;
};

// A tagged object is not allocated on the heap and cannot be retained.
try testing.expect(retainCount(obj) != 1);

// `Object.fromId` must work even when the pointer is unaligned.
const obj_ptr = @intFromPtr(obj.value);
try testing.expect(!std.mem.isAligned(obj_ptr, @alignOf(usize)));
try testing.expect(std.meta.eql(obj, Object.fromId(obj.value)));
}