@@ -1142,8 +1142,9 @@ mod simd {
1142
1142
/// guarantees.
1143
1143
///
1144
1144
/// Since `Unalign` has no alignment requirement, the inner `T` may not be
1145
- /// properly aligned in memory. There are four ways to access the inner `T`:
1145
+ /// properly aligned in memory. There are five ways to access the inner `T`:
1146
1146
/// - by value, using [`get`] or [`into_inner`]
1147
+ /// - by reference inside of a callback, using [`update`]
1147
1148
/// - fallibly by reference, using [`try_deref`] or [`try_deref_mut`]; these can
1148
1149
/// fail if the `Unalign` does not satisfy `T`'s alignment requirement at
1149
1150
/// runtime
@@ -1156,17 +1157,21 @@ mod simd {
1156
1157
/// [or ABI]: https://github.com/google/zerocopy/issues/164
1157
1158
/// [`get`]: Unalign::get
1158
1159
/// [`into_inner`]: Unalign::into_inner
1160
+ /// [`update`]: Unalign::update
1159
1161
/// [`try_deref`]: Unalign::try_deref
1160
1162
/// [`try_deref_mut`]: Unalign::try_deref_mut
1161
1163
/// [`deref_unchecked`]: Unalign::deref_unchecked
1162
1164
/// [`deref_mut_unchecked`]: Unalign::deref_mut_unchecked
1163
1165
// NOTE: This type is sound to use with types that need to be dropped. The
1164
1166
// reason is that the compiler-generated drop code automatically moves all
1165
1167
// values to aligned memory slots before dropping them in-place. This is not
1166
- // well-documented, but it's hinted at in places like [1] and [2].
1168
+ // well-documented, but it's hinted at in places like [1] and [2]. However, this
1169
+ // also means that `T` must be `Sized`; unless something changes, we can never
1170
+ // support unsized `T`. [3]
1167
1171
//
1168
1172
// [1] https://github.com/rust-lang/rust/issues/54148#issuecomment-420529646
1169
1173
// [2] https://github.com/google/zerocopy/pull/126#discussion_r1018512323
1174
+ // [3] https://github.com/google/zerocopy/issues/209
1170
1175
#[ allow( missing_debug_implementations) ]
1171
1176
#[ derive( FromZeroes , FromBytes , Unaligned , Default , Copy ) ]
1172
1177
#[ repr( C , packed) ]
@@ -1329,6 +1334,61 @@ impl<T> Unalign<T> {
1329
1334
pub fn set ( & mut self , t : T ) {
1330
1335
* self = Unalign :: new ( t) ;
1331
1336
}
1337
+
1338
+ /// Updates the inner `T` by calling a function on it.
1339
+ ///
1340
+ /// For large types, this method may be expensive, as it requires copying
1341
+ /// `2 * size_of::<T>()` bytes. \[1\]
1342
+ ///
1343
+ /// \[1\] Since the inner `T` may not be aligned, it would not be sound to
1344
+ /// invoke `f` on it directly. Instead, `update` moves it into a
1345
+ /// properly-aligned location in the local stack frame, calls `f` on it, and
1346
+ /// then moves it back to its original location in `self`.
1347
+ pub fn update < O , F : FnOnce ( & mut T ) -> O > ( & mut self , f : F ) -> O {
1348
+ // On drop, this moves `copy` out of itself and uses `ptr::write` to
1349
+ // overwrite `slf`.
1350
+ struct WriteBackOnDrop < T > {
1351
+ copy : ManuallyDrop < T > ,
1352
+ slf : * mut Unalign < T > ,
1353
+ }
1354
+
1355
+ impl < T > Drop for WriteBackOnDrop < T > {
1356
+ fn drop ( & mut self ) {
1357
+ // SAFETY: See inline comments.
1358
+ unsafe {
1359
+ // SAFETY: We never use `copy` again as required by
1360
+ // `ManuallyDrop::take`.
1361
+ let copy = ManuallyDrop :: take ( & mut self . copy ) ;
1362
+ // SAFETY: `slf` is the raw pointer value of `self`. We know
1363
+ // it is valid for writes and properly aligned because
1364
+ // `self` is a mutable reference, which guarantees both of
1365
+ // these properties.
1366
+ ptr:: write ( self . slf , Unalign :: new ( copy) ) ;
1367
+ }
1368
+ }
1369
+ }
1370
+
1371
+ // SAFETY: We know that `self` is valid for reads, properly aligned, and
1372
+ // points to an initialized `Unalign<T>` because it is a mutable
1373
+ // reference, which guarantees all of these properties.
1374
+ //
1375
+ // Since `T: !Copy`, it would be unsound in the general case to allow
1376
+ // both the original `Unalign<T>` and the copy to be used by safe code.
1377
+ // We guarantee that the copy is used to overwrite the original in the
1378
+ // `Drop::drop` impl of `WriteBackOnDrop`. So long as this `drop` is
1379
+ // called before any other safe code executes, soundness is upheld.
1380
+ // While this method can terminate in two ways (by returning normally or
1381
+ // by unwinding due to a panic in `f`), in both cases, `write_back` is
1382
+ // dropped - and its `drop` called - before any other safe code can
1383
+ // execute.
1384
+ let copy = unsafe { ptr:: read ( self ) } . into_inner ( ) ;
1385
+ let mut write_back = WriteBackOnDrop { copy : ManuallyDrop :: new ( copy) , slf : self } ;
1386
+
1387
+ let ret = f ( & mut write_back. copy ) ;
1388
+
1389
+ drop ( write_back) ;
1390
+ ret
1391
+ }
1332
1392
}
1333
1393
1334
1394
impl < T : Copy > Unalign < T > {
@@ -2949,7 +3009,7 @@ pub use alloc_support::*;
2949
3009
mod tests {
2950
3010
#![ allow( clippy:: unreadable_literal) ]
2951
3011
2952
- use core:: ops:: Deref ;
3012
+ use core:: { ops:: Deref , panic :: AssertUnwindSafe } ;
2953
3013
2954
3014
use static_assertions:: assert_impl_all;
2955
3015
@@ -3129,6 +3189,28 @@ mod tests {
3129
3189
} ;
3130
3190
}
3131
3191
3192
+ #[ test]
3193
+ fn test_unalign_update ( ) {
3194
+ let mut u = Unalign :: new ( AU64 ( 123 ) ) ;
3195
+ u. update ( |a| a. 0 += 1 ) ;
3196
+ assert_eq ! ( u. get( ) , AU64 ( 124 ) ) ;
3197
+
3198
+ // Test that, even if the callback panics, the original is still
3199
+ // correctly overwritten. Use a `Box` so that Miri is more likely to
3200
+ // catch any unsoundness (which would likely result in two `Box`es for
3201
+ // the same heap object, which is the sort of thing that Miri would
3202
+ // probably catch).
3203
+ let mut u = Unalign :: new ( Box :: new ( AU64 ( 123 ) ) ) ;
3204
+ let res = std:: panic:: catch_unwind ( AssertUnwindSafe ( || {
3205
+ u. update ( |a| {
3206
+ a. 0 += 1 ;
3207
+ panic ! ( ) ;
3208
+ } )
3209
+ } ) ) ;
3210
+ assert ! ( res. is_err( ) ) ;
3211
+ assert_eq ! ( u. into_inner( ) , Box :: new( AU64 ( 124 ) ) ) ;
3212
+ }
3213
+
3132
3214
#[ test]
3133
3215
fn test_read_write ( ) {
3134
3216
const VAL : u64 = 0x12345678 ;
0 commit comments