2
2
// SPDX-License-Identifier: Apache-2.0
3
3
4
4
use core:: slice;
5
+ use ddcommon:: error:: FfiSafeErrorMessage ;
5
6
use serde:: ser:: Error ;
6
7
use serde:: Serializer ;
7
8
use std:: borrow:: Cow ;
@@ -11,6 +12,14 @@ use std::marker::PhantomData;
11
12
use std:: os:: raw:: c_char;
12
13
use std:: str:: Utf8Error ;
13
14
15
+ #[ repr( C ) ]
16
+ #[ derive( Clone , Copy , Debug ) ]
17
+ pub enum SliceConversionError {
18
+ LargeLength ,
19
+ NullPointer ,
20
+ MisalignedPointer ,
21
+ }
22
+
14
23
#[ repr( C ) ]
15
24
#[ derive( Copy , Clone ) ]
16
25
pub struct Slice < ' a , T : ' a > {
@@ -25,6 +34,25 @@ pub struct Slice<'a, T: 'a> {
25
34
_marker : PhantomData < & ' a [ T ] > ,
26
35
}
27
36
37
+ /// # Safety
38
+ /// All strings are valid UTF-8 (enforced by using c-str literals in Rust).
39
+ unsafe impl FfiSafeErrorMessage for SliceConversionError {
40
+ fn as_ffi_str ( & self ) -> & ' static std:: ffi:: CStr {
41
+ match self {
42
+ SliceConversionError :: LargeLength => c"length was too large" ,
43
+ SliceConversionError :: NullPointer => c"null pointer with non-zero length" ,
44
+ SliceConversionError :: MisalignedPointer => c"pointer was not aligned for the type" ,
45
+ }
46
+ }
47
+ }
48
+ impl Display for SliceConversionError {
49
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
50
+ Display :: fmt ( self . as_rust_str ( ) , f)
51
+ }
52
+ }
53
+
54
+ impl core:: error:: Error for SliceConversionError { }
55
+
28
56
impl < ' a , T : ' a > core:: ops:: Deref for Slice < ' a , T > {
29
57
type Target = [ T ] ;
30
58
@@ -55,18 +83,27 @@ pub type ByteSlice<'a> = Slice<'a, u8>;
55
83
56
84
/// This exists as an intrinsic, but it is private.
57
85
pub fn is_aligned_and_not_null < T > ( ptr : * const T ) -> bool {
58
- !ptr. is_null ( ) && is_aligned ( ptr)
59
- }
60
-
61
- #[ inline]
62
- fn is_aligned < T > ( ptr : * const T ) -> bool {
63
- debug_assert ! ( !ptr. is_null( ) ) ;
64
- ptr as usize % std:: mem:: align_of :: < T > ( ) == 0
86
+ !ptr. is_null ( ) && ptr. is_aligned ( )
65
87
}
66
88
67
89
pub trait AsBytes < ' a > {
68
90
fn as_bytes ( & self ) -> & ' a [ u8 ] ;
69
91
92
+ /// Tries to interpret the structure as a slice of bytes.
93
+ ///
94
+ /// # Errors
95
+ ///
96
+ /// - Returns [`SliceConversionError::NullPointer`] if the slice has a null pointer and a
97
+ /// length other than zero. If pointer is null and length is zero, then return `Ok(&[])`
98
+ /// instead.
99
+ /// - Returns [`SliceConversionError::MisalignedPointer`] if the pointer is non-null and is not
100
+ /// aligned correctly for the type (not generally possible with types which are inherently
101
+ /// byte oriented, but is if the slice is of some other type which is being safely
102
+ /// reinterpreted as bytes).
103
+ /// - Returns [`SliceConversionError::LargeLength`] if the length of the slice exceeds
104
+ /// [`isize::MAX`].
105
+ fn try_as_bytes ( & self ) -> Result < & ' a [ u8 ] , SliceConversionError > ;
106
+
70
107
#[ inline]
71
108
fn try_to_utf8 ( & self ) -> Result < & ' a str , Utf8Error > {
72
109
std:: str:: from_utf8 ( self . as_bytes ( ) )
@@ -98,12 +135,40 @@ impl<'a> AsBytes<'a> for Slice<'a, u8> {
98
135
fn as_bytes ( & self ) -> & ' a [ u8 ] {
99
136
self . as_slice ( )
100
137
}
138
+
139
+ fn try_as_bytes ( & self ) -> Result < & ' a [ u8 ] , SliceConversionError > {
140
+ self . try_as_slice ( )
141
+ }
101
142
}
102
143
103
144
impl < ' a > AsBytes < ' a > for Slice < ' a , i8 > {
104
145
fn as_bytes ( & self ) -> & ' a [ u8 ] {
105
- // SAFETY: safe to convert *i8 to *u8 and then read it... I think.
106
- unsafe { Slice :: from_raw_parts ( self . ptr . cast ( ) , self . len ) } . as_slice ( )
146
+ #[ allow( clippy:: expect_used) ]
147
+ self . try_as_bytes ( )
148
+ . expect ( "AsBytes::as_bytes failed to convert to a Rust slice" )
149
+ }
150
+
151
+ fn try_as_bytes ( & self ) -> Result < & ' a [ u8 ] , SliceConversionError > {
152
+ self . try_as_slice ( ) . map ( |slice| {
153
+ // SAFETY: we've gone through a successful try_as_slice, so the
154
+ // enforceable characteristics such as fitting in isize::MAX are
155
+ // all good. The rest is safe only if the consumer respects the
156
+ // inherent safety requirements--doesn't give invalid length,
157
+ // pointer to invalid memory, etc.
158
+ unsafe { slice:: from_raw_parts ( slice. as_ptr ( ) . cast ( ) , self . len ) }
159
+ } )
160
+ }
161
+ }
162
+
163
+ impl < ' a > AsBytes < ' a > for & ' a [ c_char ] {
164
+ fn as_bytes ( & self ) -> & ' a [ u8 ] {
165
+ // SAFETY: We're converting from &[c_char] to &[u8] which is safe since
166
+ // they have the same layout and c_char has no unused bit patterns.
167
+ unsafe { slice:: from_raw_parts ( self . as_ptr ( ) . cast ( ) , self . len ( ) ) }
168
+ }
169
+
170
+ fn try_as_bytes ( & self ) -> Result < & ' a [ u8 ] , SliceConversionError > {
171
+ Ok ( self . as_bytes ( ) )
107
172
}
108
173
}
109
174
@@ -146,15 +211,32 @@ impl<'a, T: 'a> Slice<'a, T> {
146
211
}
147
212
148
213
pub fn as_slice ( & self ) -> & ' a [ T ] {
149
- if !self . ptr . is_null ( ) {
150
- // Crashing immediately is likely better than ignoring these.
151
- assert ! ( is_aligned( self . ptr) ) ;
152
- assert ! ( self . len <= isize :: MAX as usize ) ;
153
- unsafe { slice:: from_raw_parts ( self . ptr , self . len ) }
214
+ #[ allow( clippy:: expect_used) ]
215
+ self . try_as_slice ( )
216
+ . expect ( "ffi Slice failed to convert to a Rust slice" )
217
+ }
218
+
219
+ /// Tries to convert the FFI slice into a standard slice.
220
+ ///
221
+ /// # Errors
222
+ ///
223
+ /// 1. Fails if `self.ptr` is null and `self.len` is not zero.
224
+ /// 2. Fails if `self.ptr` is not null and is unaligned.
225
+ /// 3. Fails if `self.len` is larger than [`isize::MAX`].
226
+ pub fn try_as_slice ( & self ) -> Result < & ' a [ T ] , SliceConversionError > {
227
+ let ( ptr, len) = self . as_raw_parts ( ) ;
228
+ if !ptr. is_null ( ) {
229
+ if len > isize:: MAX as usize {
230
+ Err ( SliceConversionError :: LargeLength )
231
+ } else if !ptr. is_aligned ( ) {
232
+ Err ( SliceConversionError :: MisalignedPointer )
233
+ } else {
234
+ Ok ( unsafe { slice:: from_raw_parts ( ptr, len) } )
235
+ }
236
+ } else if len != 0 {
237
+ Err ( SliceConversionError :: NullPointer )
154
238
} else {
155
- // Crashing immediately is likely better than ignoring this.
156
- assert_eq ! ( self . len, 0 ) ;
157
- & [ ]
239
+ Ok ( & [ ] )
158
240
}
159
241
}
160
242
@@ -346,4 +428,112 @@ mod tests {
346
428
} ;
347
429
_ = dangerous. as_slice ( ) ;
348
430
}
431
+
432
+ #[ test]
433
+ fn test_try_as_slice_success ( ) {
434
+ let data: & [ u8 ] = b"hello world" ;
435
+ let slice = Slice :: from ( data) ;
436
+
437
+ let result = slice. try_as_slice ( ) ;
438
+ assert_eq ! ( result. unwrap( ) , data) ;
439
+ }
440
+
441
+ #[ test]
442
+ fn test_try_as_slice_null_zero_len ( ) {
443
+ let null_zero_len: Slice < u8 > = Slice {
444
+ ptr : ptr:: null ( ) ,
445
+ len : 0 ,
446
+ _marker : PhantomData ,
447
+ } ;
448
+
449
+ let result = null_zero_len. try_as_slice ( ) ;
450
+ assert_eq ! ( result. unwrap( ) , & [ ] ) ;
451
+ }
452
+
453
+ #[ test]
454
+ fn test_try_as_slice_null_nonzero_len ( ) {
455
+ let null_nonzero: Slice < u8 > = Slice {
456
+ ptr : ptr:: null ( ) ,
457
+ len : 5 ,
458
+ _marker : PhantomData ,
459
+ } ;
460
+
461
+ let result = null_nonzero. try_as_slice ( ) ;
462
+ assert ! ( matches!(
463
+ result. unwrap_err( ) ,
464
+ SliceConversionError :: NullPointer
465
+ ) ) ;
466
+ }
467
+
468
+ #[ test]
469
+ fn test_try_as_slice_large_length ( ) {
470
+ let large_len: Slice < u8 > = Slice {
471
+ ptr : ptr:: NonNull :: dangling ( ) . as_ptr ( ) ,
472
+ len : isize:: MAX as usize + 1 ,
473
+ _marker : PhantomData ,
474
+ } ;
475
+
476
+ let result = large_len. try_as_slice ( ) ;
477
+ assert ! ( matches!(
478
+ result. unwrap_err( ) ,
479
+ SliceConversionError :: LargeLength
480
+ ) ) ;
481
+ }
482
+
483
+ #[ test]
484
+ fn test_try_as_slice_misaligned_pointer ( ) {
485
+ // Create a misaligned pointer for u64 by using a properly aligned
486
+ // array, then offset by 1 byte.
487
+ let data = [ 0u64 ; 2 ] ;
488
+ let base_ptr = data. as_ptr ( ) . cast :: < u8 > ( ) ;
489
+ let misaligned_ptr = unsafe { base_ptr. add ( 1 ) . cast :: < u64 > ( ) } ;
490
+
491
+ // Verify the pointer is actually misaligned
492
+ assert ! ( !misaligned_ptr. is_aligned( ) ) ;
493
+
494
+ let misaligned: Slice < u64 > = Slice {
495
+ ptr : misaligned_ptr,
496
+ len : 1 ,
497
+ _marker : PhantomData ,
498
+ } ;
499
+
500
+ let result = misaligned. try_as_slice ( ) ;
501
+ assert ! ( matches!(
502
+ result. unwrap_err( ) ,
503
+ SliceConversionError :: MisalignedPointer
504
+ ) ) ;
505
+ }
506
+
507
+ #[ test]
508
+ fn test_try_as_bytes_u8_slice ( ) {
509
+ let data: & [ u8 ] = b"test data" ;
510
+ let slice = Slice :: from ( data) ;
511
+
512
+ let result = slice. try_as_bytes ( ) ;
513
+ assert_eq ! ( result. unwrap( ) , data) ;
514
+ }
515
+
516
+ #[ test]
517
+ fn test_try_as_bytes_i8_slice ( ) {
518
+ let data: & [ i8 ] = & [ 72 , 101 , 108 , 108 , 111 ] ; // "Hello" in i8
519
+ let slice = Slice :: from ( data) ;
520
+
521
+ let result = slice. try_as_bytes ( ) ;
522
+ assert_eq ! ( result. unwrap( ) , b"Hello" ) ;
523
+ }
524
+
525
+ #[ test]
526
+ fn test_try_as_bytes_char_slice_error_propagation ( ) {
527
+ let null_nonzero: Slice < c_char > = Slice {
528
+ ptr : ptr:: null ( ) ,
529
+ len : 3 ,
530
+ _marker : PhantomData ,
531
+ } ;
532
+
533
+ let result = null_nonzero. try_as_bytes ( ) ;
534
+ assert ! ( matches!(
535
+ result. unwrap_err( ) ,
536
+ SliceConversionError :: NullPointer
537
+ ) ) ;
538
+ }
349
539
}
0 commit comments