Skip to content

Commit 62629dd

Browse files
1313Copilot
andcommitted
Merge ffi/layout: compile-time + cross-language FFI ABI layout assertions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 01f0d7e + 653a93d commit 62629dd

3 files changed

Lines changed: 99 additions & 1 deletion

File tree

src/ffi/mod.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,67 @@ pub struct FFIApplicationData {
5252
pub app_name_length: u32,
5353
}
5454

55+
// MARK: - ABI Layout Assertions
56+
//
57+
// The four `#[repr(C)]` structs above are passed by value (or via packed
58+
// buffers) across the Rust <-> Swift `@_cdecl` FFI boundary. Their Swift
59+
// counterparts live in `swift-bridge/Sources/ScreenCaptureKitBridge/Core.swift`
60+
// (`@frozen public struct FFIRect/FFIDisplayData/FFIWindowData/FFIApplicationData`).
61+
//
62+
// These compile-time assertions pin the exact ABI shared with Swift: any change
63+
// to a field type, field order, or padding fails the build immediately instead
64+
// of silently corrupting marshalled data at runtime. If you change the layout
65+
// here you MUST mirror it in Core.swift (and vice versa); the cross-language
66+
// `sc_verify_ffi_layout` check in `tests/ffi_layout_tests.rs` guards that too.
67+
use core::mem::{align_of, offset_of, size_of};
68+
69+
const _: () = assert!(size_of::<FFIRect>() == 32);
70+
const _: () = assert!(align_of::<FFIRect>() == 8);
71+
const _: () = assert!(offset_of!(FFIRect, x) == 0);
72+
const _: () = assert!(offset_of!(FFIRect, y) == 8);
73+
const _: () = assert!(offset_of!(FFIRect, width) == 16);
74+
const _: () = assert!(offset_of!(FFIRect, height) == 24);
75+
76+
const _: () = assert!(size_of::<FFIDisplayData>() == 48);
77+
const _: () = assert!(align_of::<FFIDisplayData>() == 8);
78+
const _: () = assert!(offset_of!(FFIDisplayData, display_id) == 0);
79+
const _: () = assert!(offset_of!(FFIDisplayData, width) == 4);
80+
const _: () = assert!(offset_of!(FFIDisplayData, height) == 8);
81+
const _: () = assert!(offset_of!(FFIDisplayData, frame) == 16);
82+
83+
const _: () = assert!(size_of::<FFIWindowData>() == 64);
84+
const _: () = assert!(align_of::<FFIWindowData>() == 8);
85+
const _: () = assert!(offset_of!(FFIWindowData, window_id) == 0);
86+
const _: () = assert!(offset_of!(FFIWindowData, window_layer) == 4);
87+
const _: () = assert!(offset_of!(FFIWindowData, is_on_screen) == 8);
88+
const _: () = assert!(offset_of!(FFIWindowData, is_active) == 9);
89+
const _: () = assert!(offset_of!(FFIWindowData, frame) == 16);
90+
const _: () = assert!(offset_of!(FFIWindowData, title_offset) == 48);
91+
const _: () = assert!(offset_of!(FFIWindowData, title_length) == 52);
92+
const _: () = assert!(offset_of!(FFIWindowData, owning_app_index) == 56);
93+
const _: () = assert!(offset_of!(FFIWindowData, _padding) == 60);
94+
95+
const _: () = assert!(size_of::<FFIApplicationData>() == 24);
96+
const _: () = assert!(align_of::<FFIApplicationData>() == 4);
97+
const _: () = assert!(offset_of!(FFIApplicationData, process_id) == 0);
98+
const _: () = assert!(offset_of!(FFIApplicationData, _padding) == 4);
99+
const _: () = assert!(offset_of!(FFIApplicationData, bundle_id_offset) == 8);
100+
const _: () = assert!(offset_of!(FFIApplicationData, bundle_id_length) == 12);
101+
const _: () = assert!(offset_of!(FFIApplicationData, app_name_offset) == 16);
102+
const _: () = assert!(offset_of!(FFIApplicationData, app_name_length) == 20);
103+
55104
// MARK: - CoreGraphics Initialization
56105
extern "C" {
57106
/// Force CoreGraphics initialization by calling `CGMainDisplayID`
58107
/// This prevents `CGS_REQUIRE_INIT` crashes on headless systems
59108
pub fn sc_initialize_core_graphics();
109+
110+
/// Cross-language ABI check implemented in the Swift bridge.
111+
///
112+
/// Returns `true` only if the Swift `MemoryLayout` (size, stride and
113+
/// alignment) of all four FFI structs matches the values pinned on the
114+
/// Rust side. Verified by `tests/ffi_layout_tests.rs`.
115+
pub fn sc_verify_ffi_layout() -> bool;
60116
}
61117

62118
// MARK: - SCShareableContent

swift-bridge/Sources/ScreenCaptureKitBridge/Core.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@ public func initializeCoreGraphics() {
7777
_ = CGMainDisplayID()
7878
}
7979

80+
// MARK: - FFI Layout Verification
81+
82+
/// Cross-language ABI check called from Rust's `tests/ffi_layout_tests.rs`.
83+
///
84+
/// Returns `true` only if the Swift `MemoryLayout` (size, stride and alignment)
85+
/// of all four FFI structs matches the values pinned on the Rust side via the
86+
/// `const _: () = assert!(...)` checks in `src/ffi/mod.rs`. If the layouts ever
87+
/// drift apart this returns `false` and the Rust test fails, flagging a real
88+
/// ABI mismatch.
89+
@_cdecl("sc_verify_ffi_layout")
90+
public func verifyFFILayout() -> Bool {
91+
return MemoryLayout<FFIRect>.size == 32
92+
&& MemoryLayout<FFIRect>.stride == 32
93+
&& MemoryLayout<FFIRect>.alignment == 8
94+
&& MemoryLayout<FFIDisplayData>.size == 48
95+
&& MemoryLayout<FFIDisplayData>.stride == 48
96+
&& MemoryLayout<FFIDisplayData>.alignment == 8
97+
&& MemoryLayout<FFIWindowData>.size == 64
98+
&& MemoryLayout<FFIWindowData>.stride == 64
99+
&& MemoryLayout<FFIWindowData>.alignment == 8
100+
&& MemoryLayout<FFIApplicationData>.size == 24
101+
&& MemoryLayout<FFIApplicationData>.stride == 24
102+
&& MemoryLayout<FFIApplicationData>.alignment == 4
103+
}
104+
80105
// MARK: - Error Types
81106

82107
/// Strongly typed errors for the ScreenCaptureKit bridge

tests/ffi_layout_tests.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
99
use std::mem::{align_of, size_of};
1010

11-
use screencapturekit::ffi::{FFIApplicationData, FFIDisplayData, FFIRect, FFIWindowData};
11+
use screencapturekit::ffi::{
12+
sc_verify_ffi_layout, FFIApplicationData, FFIDisplayData, FFIRect, FFIWindowData,
13+
};
1214

1315
#[test]
1416
fn ffi_rect_layout() {
@@ -57,3 +59,18 @@ fn ffi_application_data_layout() {
5759
"FFIApplicationData alignment drifted"
5860
);
5961
}
62+
63+
/// Cross-language ABI check: asks the Swift bridge to verify that *its*
64+
/// `MemoryLayout` (size/stride/alignment) for all four FFI structs matches the
65+
/// values pinned on the Rust side. A `false` return means the Rust and Swift
66+
/// layouts genuinely disagree, which is a real ABI bug.
67+
#[test]
68+
fn ffi_layout_matches_swift() {
69+
// SAFETY: `sc_verify_ffi_layout` takes no arguments and only reads
70+
// compile-time `MemoryLayout` constants in the Swift bridge.
71+
let matches = unsafe { sc_verify_ffi_layout() };
72+
assert!(
73+
matches,
74+
"Swift FFI struct layout disagrees with Rust layout (ABI mismatch)"
75+
);
76+
}

0 commit comments

Comments
 (0)