Skip to content

Commit 093f70a

Browse files
Fix soundness issue: avoid UB in swap_columns/swap_elements when indices alias
When swap_columns is called with a == b, the previous implementation created two mutable references (&mut self[a] and &mut self[b]) to the same memory location (aliased &mut UB). Additionally, even for distinct indices a != b, using ptr::swap(&mut self[a], &mut self[b]) is UB under Miri (Stacked Borrows): the second IndexMut call creates a new unique reborrow of all of self, invalidating the tag from the first call. The same class of bug existed in: - Array::swap_elements (structure.rs) - Matrix{2,3,4}::swap_columns - Matrix{2,3,4}::swap_elements Fix: since S: BaseFloat implies S: Copy, column vectors and scalar elements are Copy. We can swap entirely without unsafe by using a temporary variable: let tmp = self[a]; self[a] = self[b]; self[b] = tmp; This avoids creating multiple mutable references simultaneously and requires no unsafe code at all. Verified clean under Miri. Tests added in tests/matrix.rs covering all three matrix sizes for: - swap_columns with equal indices (no-op, was UB) - swap_columns with distinct indices (correct swap) - swap_elements with same position (no-op, was UB) - swap_elements with same column, different rows Note: the tests verify correctness but do not catch the soundness bug in a regular cargo test run. Use cargo +nightly miri test to detect aliasing UB. Fixes: #565 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent ff840cb commit 093f70a

3 files changed

Lines changed: 177 additions & 10 deletions

File tree

src/matrix.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -580,14 +580,26 @@ impl<S: BaseFloat> Matrix for Matrix2<S> {
580580

581581
#[inline]
582582
fn swap_columns(&mut self, a: usize, b: usize) {
583-
unsafe { ptr::swap(&mut self[a], &mut self[b]) };
583+
if a == b {
584+
return;
585+
}
586+
// S: BaseFloat implies S: Copy, so column vectors are Copy — no unsafe needed.
587+
let tmp = self[a];
588+
self[a] = self[b];
589+
self[b] = tmp;
584590
}
585591

586592
#[inline]
587593
fn swap_elements(&mut self, a: (usize, usize), b: (usize, usize)) {
594+
if a == b {
595+
return;
596+
}
588597
let (ac, ar) = a;
589598
let (bc, br) = b;
590-
unsafe { ptr::swap(&mut self[ac][ar], &mut self[bc][br]) };
599+
// S: BaseFloat implies S: Copy — swap by value without unsafe.
600+
let tmp = self[ac][ar];
601+
self[ac][ar] = self[bc][br];
602+
self[bc][br] = tmp;
591603
}
592604

593605
fn transpose(&self) -> Matrix2<S> {
@@ -679,14 +691,26 @@ impl<S: BaseFloat> Matrix for Matrix3<S> {
679691

680692
#[inline]
681693
fn swap_columns(&mut self, a: usize, b: usize) {
682-
unsafe { ptr::swap(&mut self[a], &mut self[b]) };
694+
if a == b {
695+
return;
696+
}
697+
// S: BaseFloat implies S: Copy, so column vectors are Copy — no unsafe needed.
698+
let tmp = self[a];
699+
self[a] = self[b];
700+
self[b] = tmp;
683701
}
684702

685703
#[inline]
686704
fn swap_elements(&mut self, a: (usize, usize), b: (usize, usize)) {
705+
if a == b {
706+
return;
707+
}
687708
let (ac, ar) = a;
688709
let (bc, br) = b;
689-
unsafe { ptr::swap(&mut self[ac][ar], &mut self[bc][br]) };
710+
// S: BaseFloat implies S: Copy — swap by value without unsafe.
711+
let tmp = self[ac][ar];
712+
self[ac][ar] = self[bc][br];
713+
self[bc][br] = tmp;
690714
}
691715

692716
fn transpose(&self) -> Matrix3<S> {
@@ -795,14 +819,26 @@ impl<S: BaseFloat> Matrix for Matrix4<S> {
795819

796820
#[inline]
797821
fn swap_columns(&mut self, a: usize, b: usize) {
798-
unsafe { ptr::swap(&mut self[a], &mut self[b]) };
822+
if a == b {
823+
return;
824+
}
825+
// S: BaseFloat implies S: Copy, so column vectors are Copy — no unsafe needed.
826+
let tmp = self[a];
827+
self[a] = self[b];
828+
self[b] = tmp;
799829
}
800830

801831
#[inline]
802832
fn swap_elements(&mut self, a: (usize, usize), b: (usize, usize)) {
833+
if a == b {
834+
return;
835+
}
803836
let (ac, ar) = a;
804837
let (bc, br) = b;
805-
unsafe { ptr::swap(&mut self[ac][ar], &mut self[bc][br]) };
838+
// S: BaseFloat implies S: Copy — swap by value without unsafe.
839+
let tmp = self[ac][ar];
840+
self[ac][ar] = self[bc][br];
841+
self[bc][br] = tmp;
806842
}
807843

808844
fn transpose(&self) -> Matrix4<S> {

src/structure.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@ where
7272
/// Swap the elements at indices `i` and `j` in-place.
7373
#[inline]
7474
fn swap_elements(&mut self, i: usize, j: usize) {
75-
use std::ptr;
76-
77-
// Yeah, ok borrow checker – I know what I'm doing here
78-
unsafe { ptr::swap(&mut self[i], &mut self[j]) };
75+
if i == j {
76+
return;
77+
}
78+
// Element: Copy is a supertrait bound, so we can swap without unsafe.
79+
let tmp = self[i];
80+
self[i] = self[j];
81+
self[j] = tmp;
7982
}
8083

8184
/// The sum of the elements of the array.

tests/matrix.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,134 @@
1616
extern crate approx;
1717
extern crate cgmath;
1818

19+
/// Tests for swap_columns, swap_rows, and swap_elements correctness and soundness
20+
/// (see https://github.com/rustgd/cgmath/issues/565).
21+
pub mod swap {
22+
use cgmath::*;
23+
24+
#[test]
25+
fn matrix2_swap_columns_same_index() {
26+
let original = Matrix2::new(1.0f64, 2.0, 3.0, 4.0);
27+
let mut m = original;
28+
m.swap_columns(0, 0);
29+
assert_eq!(m, original);
30+
m.swap_columns(1, 1);
31+
assert_eq!(m, original);
32+
}
33+
34+
#[test]
35+
fn matrix2_swap_columns_different_indices() {
36+
let mut m = Matrix2::new(1.0f64, 2.0, 3.0, 4.0);
37+
m.swap_columns(0, 1);
38+
assert_eq!(m, Matrix2::new(3.0, 4.0, 1.0, 2.0));
39+
// swapping back gives original
40+
m.swap_columns(1, 0);
41+
assert_eq!(m, Matrix2::new(1.0, 2.0, 3.0, 4.0));
42+
}
43+
44+
#[test]
45+
fn matrix2_swap_elements_same_position() {
46+
let original = Matrix2::new(1.0f64, 2.0, 3.0, 4.0);
47+
let mut m = original;
48+
m.swap_elements((0, 0), (0, 0));
49+
assert_eq!(m, original);
50+
m.swap_elements((1, 1), (1, 1));
51+
assert_eq!(m, original);
52+
}
53+
54+
#[test]
55+
fn matrix2_swap_elements_same_column_different_row() {
56+
// column 0 has elements [1.0 (row0), 2.0 (row1)]
57+
let mut m = Matrix2::new(1.0f64, 2.0, 3.0, 4.0);
58+
m.swap_elements((0, 0), (0, 1));
59+
assert_eq!(m, Matrix2::new(2.0, 1.0, 3.0, 4.0));
60+
}
61+
62+
#[test]
63+
fn matrix3_swap_columns_same_index() {
64+
let original = Matrix3::new(1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
65+
let mut m = original;
66+
m.swap_columns(0, 0);
67+
assert_eq!(m, original);
68+
m.swap_columns(1, 1);
69+
assert_eq!(m, original);
70+
m.swap_columns(2, 2);
71+
assert_eq!(m, original);
72+
}
73+
74+
#[test]
75+
fn matrix3_swap_columns_different_indices() {
76+
let mut m = Matrix3::new(1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
77+
let col0_before = m[0];
78+
let col2_before = m[2];
79+
m.swap_columns(0, 2);
80+
assert_eq!(m[0], col2_before);
81+
assert_eq!(m[2], col0_before);
82+
}
83+
84+
#[test]
85+
fn matrix3_swap_elements_same_position() {
86+
let original = Matrix3::new(1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
87+
let mut m = original;
88+
m.swap_elements((1, 1), (1, 1));
89+
assert_eq!(m, original);
90+
}
91+
92+
#[test]
93+
fn matrix3_swap_elements_same_column_different_row() {
94+
let mut m = Matrix3::new(1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
95+
m.swap_elements((1, 0), (1, 2));
96+
// column 1 had [4, 5, 6]; after swap row0 and row2: [6, 5, 4]
97+
assert_eq!(m[1], Vector3::new(6.0, 5.0, 4.0));
98+
}
99+
100+
#[test]
101+
fn matrix4_swap_columns_same_index() {
102+
let original = Matrix4::new(
103+
1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
104+
16.0,
105+
);
106+
let mut m = original;
107+
m.swap_columns(2, 2);
108+
assert_eq!(m, original);
109+
}
110+
111+
#[test]
112+
fn matrix4_swap_columns_different_indices() {
113+
let mut m = Matrix4::new(
114+
1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
115+
16.0,
116+
);
117+
let col1_before = m[1];
118+
let col3_before = m[3];
119+
m.swap_columns(1, 3);
120+
assert_eq!(m[1], col3_before);
121+
assert_eq!(m[3], col1_before);
122+
}
123+
124+
#[test]
125+
fn matrix4_swap_elements_same_position() {
126+
let original = Matrix4::new(
127+
1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
128+
16.0,
129+
);
130+
let mut m = original;
131+
m.swap_elements((2, 2), (2, 2));
132+
assert_eq!(m, original);
133+
}
134+
135+
#[test]
136+
fn matrix4_swap_elements_same_column_different_row() {
137+
let mut m = Matrix4::new(
138+
1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
139+
16.0,
140+
);
141+
// column 2 has rows [9, 10, 11, 12]; swap row 0 and row 3
142+
m.swap_elements((2, 0), (2, 3));
143+
assert_eq!(m[2], Vector4::new(12.0, 10.0, 11.0, 9.0));
144+
}
145+
}
146+
19147
pub mod matrix2 {
20148
use std::f64;
21149

0 commit comments

Comments
 (0)