Skip to content

Commit 191b5bf

Browse files
committed
Fix structs that have packed alignment
`header-translator` did not correctly catch `#pragma pack(push, ...)` since libclang doesn't easily expose that. We now calculate the natural alignment of the struct, and compare it to the actual alignment, so alignment attributes should now be (at least more) correct. Fixes #758.
1 parent 4ec2bd6 commit 191b5bf

File tree

6 files changed

+105
-10
lines changed

6 files changed

+105
-10
lines changed

crates/header-translator/src/stmt.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::cmp::Ordering;
23
use std::collections::BTreeMap;
34
use std::collections::BTreeSet;
45
use std::collections::HashMap;
@@ -551,7 +552,8 @@ pub enum Stmt {
551552
boxable: bool,
552553
fields: Vec<(String, Documentation, Ty)>,
553554
sendable: Option<bool>,
554-
packed: bool,
555+
align: usize,
556+
natural_align: usize,
555557
documentation: Documentation,
556558
is_union: bool,
557559
},
@@ -1294,6 +1296,8 @@ impl Stmt {
12941296
let mut fields = Vec::new();
12951297
let mut sendable = None;
12961298
let mut packed = false;
1299+
let align = ty.get_alignof().expect("alignment of record type");
1300+
let mut natural_align = 0;
12971301

12981302
let mut res = vec![];
12991303

@@ -1313,6 +1317,17 @@ impl Stmt {
13131317
let _span = debug_span!("field", name).entered();
13141318

13151319
let ty = entity.get_type().expect("struct/union field type");
1320+
let field_align = ty.get_alignof().unwrap();
1321+
if align < field_align {
1322+
// Similar to what bindgen does, we cannot detect
1323+
// #pragma pack, but we can detect it's effect
1324+
// (that the type itself has lower alignment than
1325+
// its fields).
1326+
packed = true;
1327+
}
1328+
// Compute the natural alignment of the struct.
1329+
natural_align = natural_align.max(field_align);
1330+
13161331
let ty = Ty::parse_record_field(ty, context);
13171332

13181333
if entity.is_bit_field() {
@@ -1330,7 +1345,9 @@ impl Stmt {
13301345
// emit them at the top-level.
13311346
res.extend(Self::parse(&entity, context, current_library));
13321347
}
1333-
EntityKind::PackedAttr => packed = true,
1348+
EntityKind::PackedAttr => {
1349+
assert_eq!(align, 1, "packed records must have an alignment of 1");
1350+
}
13341351
EntityKind::VisibilityAttr => {}
13351352
_ => error!(?entity, "unknown struct/union child"),
13361353
});
@@ -1347,7 +1364,8 @@ impl Stmt {
13471364
boxable,
13481365
fields,
13491366
sendable,
1350-
packed,
1367+
align,
1368+
natural_align,
13511369
documentation,
13521370
is_union,
13531371
});
@@ -2517,18 +2535,23 @@ impl Stmt {
25172535
boxable: _,
25182536
fields,
25192537
sendable,
2520-
packed,
2538+
align,
2539+
natural_align,
25212540
documentation,
25222541
is_union,
25232542
} => {
25242543
write!(f, "{}", documentation.fmt(Some(id)))?;
25252544
write!(f, "{}", self.cfg_gate_ln(config))?;
25262545
write!(f, "{availability}")?;
2527-
if *packed {
2528-
writeln!(f, "#[repr(C, packed)]")?;
2529-
} else {
2530-
writeln!(f, "#[repr(C)]")?;
2546+
2547+
// See <https://doc.rust-lang.org/reference/type-layout.html#the-alignment-modifiers>
2548+
match align.cmp(natural_align) {
2549+
Ordering::Less if *align == 1 => writeln!(f, "#[repr(C, packed)]")?,
2550+
Ordering::Less => writeln!(f, "#[repr(C, packed({align}))]")?,
2551+
Ordering::Equal => writeln!(f, "#[repr(C)]")?,
2552+
Ordering::Greater => writeln!(f, "#[repr(C, align({align}))]")?,
25312553
}
2554+
25322555
if *is_union || fields.iter().any(|(_, _, field)| field.contains_union()) {
25332556
writeln!(f, "#[derive(Clone, Copy)]")?;
25342557
} else {

crates/objc2/src/topics/about_generated/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
2727
takes a nullable file name.
2828
* **BREAKING**: The media selection option on `AVAssetVariantQualifier` is now nullable.
2929

30+
### Fixed
31+
* **BREAKING**: Fix structs with packed alignment by marking them `#[repr(packed(...))]`.
32+
3033
## [0.3.1] - 2025-04-19
3134
[0.3.1]: https://github.com/madsmtm/objc2/compare/frameworks-0.3.0...frameworks-0.3.1
3235

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Regression test for <https://github.com/madsmtm/objc2/issues/758>.
2+
#![cfg(feature = "CMBlockBuffer")]
3+
use objc2_core_foundation::{kCFAllocatorDefault, kCFAllocatorNull, CFRetained};
4+
use objc2_core_media::{
5+
kCMBlockBufferCustomBlockSourceVersion, kCMBlockBufferNoErr, CMBlockBuffer,
6+
CMBlockBufferCustomBlockSource,
7+
};
8+
use std::{ffi::c_void, ptr::NonNull};
9+
10+
unsafe extern "C-unwind" fn free_block(
11+
ref_context: *mut c_void,
12+
block: NonNull<c_void>,
13+
size: usize,
14+
) {
15+
let data = unsafe { Vec::from_raw_parts(block.as_ptr().cast::<u8>(), 2, 2) };
16+
println!("Freeing block of size: {size}, {data:?}");
17+
let ref_value = unsafe { *(ref_context.cast::<i32>()) };
18+
println!("Getting RefCon: {ref_value}");
19+
}
20+
21+
#[test]
22+
fn main() {
23+
let data_size = 2;
24+
let data = vec![b'A'; data_size];
25+
let data = data.leak(); // we free this at `free_block`
26+
27+
let mut ref_data = 42;
28+
let mut source: CMBlockBufferCustomBlockSource = unsafe { std::mem::zeroed() };
29+
source.version = kCMBlockBufferCustomBlockSourceVersion;
30+
source.FreeBlock = Some(free_block);
31+
let ref_data: *mut i32 = &mut ref_data;
32+
source.refCon = ref_data.cast::<c_void>();
33+
34+
let mut block_buffer: *mut CMBlockBuffer = std::ptr::null_mut();
35+
let status = unsafe {
36+
CMBlockBuffer::create_with_memory_block(
37+
kCFAllocatorDefault,
38+
data.as_mut_ptr().cast(),
39+
data_size,
40+
kCFAllocatorNull,
41+
&source,
42+
0,
43+
data_size,
44+
0,
45+
NonNull::from(&mut block_buffer),
46+
)
47+
};
48+
assert_eq!(status, kCMBlockBufferNoErr); // fail to create block buffer
49+
let block_buffer = unsafe { CFRetained::from_raw(NonNull::new(block_buffer).unwrap()) };
50+
assert_eq!(block_buffer.retain_count(), 1);
51+
}

framework-crates/objc2-core-services/src/files.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use crate::UniChar;
77
// act as-if CoreServices is the one to define this type.
88
//
99
// NOTE: This is marked __attribute__((aligned(2), packed)), but that's
10-
// unnecessary, since the layout of the .
10+
// unnecessary, since the alignment of u16/UniChar, and thereby the struct
11+
// itself, is already 2.
1112
#[repr(C)]
1213
#[derive(Clone, Copy, Debug, PartialEq)]
1314
pub struct HFSUniStr255 {
@@ -27,3 +28,13 @@ unsafe impl Encode for HFSUniStr255 {
2728
unsafe impl RefEncode for HFSUniStr255 {
2829
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
2930
}
31+
32+
#[cfg(test)]
33+
mod tests {
34+
use super::*;
35+
36+
#[test]
37+
fn alignment() {
38+
assert_eq!(core::mem::align_of::<HFSUniStr255>(), 2);
39+
}
40+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![cfg(all(feature = "usb", feature = "AppleUSBDefinitions"))]
2+
3+
#[test]
4+
fn alignment() {
5+
assert_eq!(std::mem::align_of::<objc2_io_kit::IOUSBHIDReportDesc>(), 1);
6+
assert_eq!(std::mem::size_of::<objc2_io_kit::IOUSBHIDReportDesc>(), 3);
7+
}

0 commit comments

Comments
 (0)