Skip to content

Commit e1928da

Browse files
Fix alignment of large structs in unions (#289)
The template the generates the `planus::WriteAsOffset` impl for structs (inside the file `planus-codegen/src/templates/rust/struct.template`) called the function `builder.write_with` using an `alignment` argument instead of an `alignment_mask` argument. This meant that the calculations for how many bytes of padding is needed for this struct will be wrong. This can cause too little padding for any structs that was put inside unions, and additionally it could cause more than strictly necessary amounts of padding for all other subsequently serialized values. This bug did **not** cause any undefined behavior inside planus itself. Other programs were dependent on having valid requirements, then these programs might not work correctly for data generated by planus. All code affected by this problem will panic if run with debug assertions on.
1 parent 060ffc7 commit e1928da

File tree

18 files changed

+168
-7
lines changed

18 files changed

+168
-7
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Fixed
1313

14+
- \[Rust\]: Fix the alignment of structs in unions [#289](https://github.com/planus-org/planus/pull/289)
15+
1416
### Removed
1517

1618
## [1.1.0] - 2025-03-02

RELEASE-CHECKLIST.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Grep for the old version number
66
- Run `cargo make regenerate-examples`
77
- Update CHANGELOG.md, including links at the bottom
8+
- Remember to update `Cargo.lock`
89
- Commit changes to a branch and make a PR
910
- Run `cargo publish --dry-run` on PR branch
1011
- Wait for CI and merge PR to main branch

crates/planus-codegen/src/templates/rust/struct.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl ::planus::WriteAsOffset<{{ info.owned_name }}> for {{ info.owned_name }} {
4343
unsafe {
4444
builder.write_with(
4545
{{ size }},
46-
{{ alignment }},
46+
{{ alignment - 1 }},
4747
|buffer_position, bytes| {
4848
let bytes = bytes.as_mut_ptr();
4949

examples/rust/src/monster_generated.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ mod root {
462462
#[inline]
463463
fn prepare(&self, builder: &mut ::planus::Builder) -> ::planus::Offset<Vec3> {
464464
unsafe {
465-
builder.write_with(12, 4, |buffer_position, bytes| {
465+
builder.write_with(12, 3, |buffer_position, bytes| {
466466
let bytes = bytes.as_mut_ptr();
467467

468468
::planus::WriteAsPrimitive::write(

test/Cargo.lock

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
struct Struct {
2+
value: uint64;
3+
}
4+
5+
union Union {
6+
Struct,
7+
String: string
8+
}
9+
10+
table Root {
11+
unions: [Union];
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use planus::ReadAsRoot;
2+
3+
#[track_caller]
4+
fn eq<I1, I2>(iter1: I1, iter2: I2)
5+
where
6+
I1: Clone + Iterator + ExactSizeIterator,
7+
I2: Clone + Iterator + ExactSizeIterator,
8+
I1::Item: core::fmt::Debug + PartialEq<I2::Item>,
9+
I2::Item: core::fmt::Debug,
10+
{
11+
if !iter1.clone().eq(iter2.clone()) {
12+
let iter1 = iter1.collect::<Vec<_>>();
13+
let iter2 = iter2.collect::<Vec<_>>();
14+
panic!(
15+
"Not equal: {iter1:?} {iter2:?}",
16+
iter1 = iter1,
17+
iter2 = iter2
18+
);
19+
}
20+
let mut len = iter2.len();
21+
assert_eq!(len, iter1.len());
22+
let mut iter2 = iter2;
23+
while let Some(_) = iter2.next() {
24+
assert_eq!(iter2.len(), len - 1);
25+
len -= 1;
26+
}
27+
}
28+
29+
fn to_owned_vec<'buf>(vec: planus::UnionVector<'buf, UnionRef<'buf>>) -> Vec<Union> {
30+
vec.iter().map(|v| v.unwrap().try_into().unwrap()).collect()
31+
}
32+
33+
#[track_caller]
34+
fn cmp<I1, I2, F, T>(iter1: I1, iter2: I2, f: F)
35+
where
36+
I1: Clone + Iterator + DoubleEndedIterator + ExactSizeIterator,
37+
I2: Clone + Iterator + DoubleEndedIterator + ExactSizeIterator,
38+
I1::Item: core::fmt::Debug + PartialEq<T>,
39+
F: Copy + Fn(I2::Item) -> T,
40+
T: core::fmt::Debug,
41+
{
42+
eq(iter1.clone(), iter2.clone().map(f));
43+
eq(iter1.clone().rev(), iter2.clone().rev().map(f));
44+
for i in 1..=iter1.len() + 1 {
45+
eq(iter1.clone().step_by(i), iter2.clone().step_by(i).map(f));
46+
eq(
47+
iter1.clone().step_by(i).rev(),
48+
iter2.clone().step_by(i).rev().map(f),
49+
);
50+
eq(
51+
iter1.clone().rev().step_by(i),
52+
iter2.clone().rev().step_by(i).map(f),
53+
);
54+
}
55+
}
56+
57+
fn run_test(values: &[Union]) {
58+
let mut builder = planus::Builder::new();
59+
for i in 0..=values.len() {
60+
builder.clear();
61+
let root = Root::create(&mut builder, &values[..i]);
62+
let data = builder.finish(root, None);
63+
let root = RootRef::read_as_root(data).unwrap();
64+
let vector = root.unions().unwrap().unwrap();
65+
let owned: Vec<Union> = to_owned_vec(vector);
66+
assert_eq!(owned, &values[..i]);
67+
68+
cmp(
69+
owned.iter().cloned(),
70+
vector.iter(),
71+
|u: Result<UnionRef<'_>, _>| -> Union { u.unwrap().try_into().unwrap() },
72+
);
73+
74+
for i in 1..=values.len() + 1 {
75+
cmp(owned.chunks(i), vector.chunks(i), to_owned_vec);
76+
cmp(owned.chunks_exact(i), vector.chunks_exact(i), to_owned_vec);
77+
cmp(owned.rchunks(i), vector.rchunks(i), to_owned_vec);
78+
cmp(
79+
owned.rchunks_exact(i),
80+
vector.rchunks_exact(i),
81+
to_owned_vec,
82+
);
83+
cmp(owned.windows(i), vector.windows(i), to_owned_vec);
84+
}
85+
}
86+
}
87+
88+
run_test(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(|value| Union::Struct(Struct { value })));
89+
run_test(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(|value| Union::String(format!("{value}"))));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
struct Struct64 {
2+
value: uint64;
3+
}
4+
5+
table Root {
6+
value: Struct64;
7+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Root {
2+
value: Some(
3+
Struct64 {
4+
value: 17,
5+
},
6+
),
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
obj @ 0x04..0x10
2+
vtable @ 0x10..0x16
3+
field[0] @ 0x08..0x10:
4+
11 00 00 00 00 00 00 00
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"value": {
3+
"value": 17
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
RootRef {
2+
value: Ok(
3+
Struct64Ref {
4+
value: 17,
5+
},
6+
),
7+
}

test/rust-test-2021/test_files_no_flatc/union.fbs

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ union Union {
66
String: string,
77
InnerTable,
88
InnerStruct,
9+
InnerStruct64,
910
}
1011

1112
table Root {
@@ -19,3 +20,7 @@ table InnerTable {
1920
struct InnerStruct {
2021
value: uint8;
2122
}
23+
24+
struct InnerStruct64 {
25+
value: uint64;
26+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
obj @ 0x04..0x0d
2+
vtable @ 0x10..0x18
3+
field[0] @ 0x0c..0x0d:
4+
04
5+
field[1] @ 0x08..0x0c:
6+
10 00 00 00
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"value": {
3+
"InnerStruct64": {
4+
"value": 17
5+
}
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
RootRef {
2+
value: Ok(
3+
InnerStruct64(
4+
InnerStruct64Ref {
5+
value: 17,
6+
},
7+
),
8+
),
9+
}

0 commit comments

Comments
 (0)