@@ -9,13 +9,10 @@ use std::fmt::Display;
99use std:: mem:: ManuallyDrop ;
1010use std:: ptr:: { null, NonNull } ;
1111
12- const FLAG_OK : usize = 0b00 ;
13- const FLAG_STATIC : usize = 0b01 ;
14- const FLAG_ALLOCATED : usize = 0b11 ;
15-
16- const MASK_IS_ERROR : usize = 0b01 ;
17- const MASK_IS_ALLOCATED : usize = 0b10 ;
18- const MASK_UNUSED : usize = !( MASK_IS_ERROR | MASK_IS_ALLOCATED ) ;
12+ // ProfileStatus uses `err` being null to encode OK, so we only need
13+ // one bit in flags to distinguish between STATIC and ALLOCATED errors.
14+ const FLAG_STATIC : usize = 0 ;
15+ const FLAG_ALLOCATED : usize = 1 ;
1916
2017/// Represents the result of an operation that either succeeds with no value, or fails with an
2118/// error message. This is like `Result<(), Cow<'static, CStr>` except its representation is
@@ -37,22 +34,21 @@ const MASK_UNUSED: usize = !(MASK_IS_ERROR | MASK_IS_ALLOCATED);
3734#[ repr( C ) ]
3835#[ derive( Debug ) ]
3936pub struct ProfileStatus {
40- /// Bitflags indicating the status and storage type.
41- /// - `FLAG_OK` (0): Success, no error. `err` must be null. From C, this is the only thing you
42- /// should check; the other flags are internal details.
43- /// - `FLAG_STATIC`: Error message points to static data. `err` is non-null and points to a
44- /// `&'static CStr`. Must not be freed.
45- /// - `FLAG_ALLOCATED`: Error message is heap-allocated. `err` is non-null and points to a
46- /// heap-allocated, null-terminated string that this `ProfileStatus` owns. Must be freed via
47- /// [`ddog_prof_Status_drop`].
37+ /// Bitflags indicating the storage type of the error message.
38+ /// This is only meaningful when `err` is non-null. When `err` is
39+ /// null (indicating OK), this field is ignored (but typically 0).
40+ /// - `FLAG_STATIC` (0): Error message points to static data that must not be freed.
41+ /// - `FLAG_ALLOCATED` (1): Error message is heap-allocated and owned by this `ProfileStatus`.
42+ /// Must be freed via [`ddog_prof_Status_drop`].
4843 pub flags : libc:: size_t ,
4944
5045 /// Pointer to a null-terminated UTF-8 error message string.
51- /// - If `flags == FLAG_OK`, this **must** be null.
52- /// - If `flags & FLAG_STATIC`, this points to static data with lifetime `'static`.
53- /// - If `flags & FLAG_ALLOCATED`, this points to heap-allocated data owned by this
54- /// `ProfileStatus`. The allocation was created by the global allocator and must be freed by
55- /// [`ddog_prof_Status_drop`].
46+ /// - If null: indicates OK (success). Check this from C to determine if the operation
47+ /// succeeded.
48+ /// - If non-null and `flags == FLAG_STATIC`: points to static data with lifetime `'static`.
49+ /// Must not be freed.
50+ /// - If non-null and `flags == FLAG_ALLOCATED`: points to heap-allocated data owned by this
51+ /// `ProfileStatus`. Must be freed by [`ddog_prof_Status_drop`].
5652 ///
5753 /// # Safety Invariant
5854 ///
@@ -75,7 +71,7 @@ impl Default for ProfileStatus {
7571// SAFETY: ProfileStatus is Send because:
7672// 1. The `flags` field is a usize, which is Send.
7773// 2. The `err` pointer is either:
78- // - Null (FLAG_OK ), which is trivially Send
74+ // - Null (OK status ), which is trivially Send
7975// - Points to static data (FLAG_STATIC), which is 'static and therefore Send
8076// - Points to heap-allocated data (FLAG_ALLOCATED) that this ProfileStatus owns exclusively.
8177// When sent to another thread, the ownership of the allocation transfers with it, and the drop
@@ -86,6 +82,7 @@ unsafe impl Send for ProfileStatus {}
8682// SAFETY: ProfileStatus is Sync because:
8783// 1. All fields are immutable from a shared reference (&ProfileStatus).
8884// 2. The `err` pointer points to immutable data:
85+ // - Null (OK status): trivially Sync
8986// - Static CStr (FLAG_STATIC): &'static CStr is Sync
9087// - Heap-allocated CStr (FLAG_ALLOCATED): The CStr is never mutated after creation, so multiple
9188// threads can safely read it concurrently.
@@ -129,61 +126,53 @@ impl From<CString> for ProfileStatus {
129126 }
130127}
131128
129+ #[ derive( thiserror:: Error , Debug ) ]
130+ pub enum TryFromProfileStatusError {
131+ #[ error( "failed to convert profile status because the pointer was null" ) ]
132+ Null ,
133+ #[ error(
134+ "failed to convert profile status because the flags were incorrect for the conversion: `{0}`"
135+ ) ]
136+ IncorrectFlags ( usize ) ,
137+ }
138+
132139impl TryFrom < ProfileStatus > for CString {
133- type Error = usize ;
140+ type Error = TryFromProfileStatusError ;
134141
135142 fn try_from ( status : ProfileStatus ) -> Result < Self , Self :: Error > {
136- if status. flags == FLAG_ALLOCATED {
143+ if status. err . is_null ( ) {
144+ Err ( TryFromProfileStatusError :: Null )
145+ } else if status. flags == FLAG_ALLOCATED {
137146 Ok ( unsafe { CString :: from_raw ( status. err . cast_mut ( ) ) } )
138147 } else {
139- Err ( status. flags )
148+ Err ( TryFromProfileStatusError :: IncorrectFlags ( status. flags ) )
140149 }
141150 }
142151}
143152
144153impl TryFrom < & ProfileStatus > for & CStr {
145- type Error = usize ;
154+ type Error = TryFromProfileStatusError ;
146155
147156 fn try_from ( status : & ProfileStatus ) -> Result < Self , Self :: Error > {
148- if status. flags != FLAG_OK {
149- Ok ( unsafe { CStr :: from_ptr ( status . err . cast_mut ( ) ) } )
157+ if status. err . is_null ( ) {
158+ Err ( TryFromProfileStatusError :: Null )
150159 } else {
151- Err ( status. flags )
160+ Ok ( unsafe { CStr :: from_ptr ( status. err . cast_mut ( ) ) } )
152161 }
153162 }
154163}
155164
156165impl From < ProfileStatus > for Result < ( ) , Cow < ' static , CStr > > {
157166 fn from ( status : ProfileStatus ) -> Self {
158- let flags = status. flags ;
159- let is_error = ( flags & MASK_IS_ERROR ) != 0 ;
160- let is_allocated = ( flags & MASK_IS_ALLOCATED ) != 0 ;
161- #[ allow( clippy:: panic) ]
162- if cfg ! ( debug_assertions) && ( status. flags & MASK_UNUSED ) != 0 {
163- panic ! ( "invalid bit pattern: {flags:b}" ) ;
164- }
165-
166- // There are 4 cases:
167- // - not-allocated, not-error -> Ok(())
168- // - not-allocated, error -> Err(Cow::Borrowed)
169- // - allocated, error -> Err(Cow::Owned)
170- // - allocated, not-error -> nonsense, panic! in debug mode
171- match ( is_allocated, is_error) {
172- ( false , false ) => Ok ( ( ) ) ,
173- ( false , true ) => Err ( Cow :: Borrowed ( unsafe { CStr :: from_ptr ( status. err ) } ) ) ,
174- ( true , true ) => Err ( Cow :: Owned ( unsafe {
167+ if status. err . is_null ( ) {
168+ Ok ( ( ) )
169+ } else if status. flags == FLAG_ALLOCATED {
170+ Err ( Cow :: Owned ( unsafe {
175171 CString :: from_raw ( status. err . cast_mut ( ) )
176- } ) ) ,
177- // This would mean there's an allocated error, but there isn't an
178- // error. That doesn't make sense, and the Rust code doesn't make
179- // them, only FFI might (but that would violate the usage docs).
180- ( true , false ) => {
181- #[ allow( clippy:: panic) ]
182- if cfg ! ( debug_assertions) {
183- panic ! ( "invalid bit pattern: {flags:b}" ) ;
184- }
185- Err ( Cow :: Borrowed ( c"error: invalid ProfileStatus flags detected while converting to Result<(), Cow<'static, CStr>>" ) )
186- }
172+ } ) )
173+ } else {
174+ // STATIC error (flags == FLAG_STATIC)
175+ Err ( Cow :: Borrowed ( unsafe { CStr :: from_ptr ( status. err ) } ) )
187176 }
188177 }
189178}
@@ -255,7 +244,7 @@ pub(crate) fn string_try_shrink_to_fit(string: &mut String) -> Result<(), AllocE
255244
256245impl ProfileStatus {
257246 pub const OK : ProfileStatus = ProfileStatus {
258- flags : FLAG_OK ,
247+ flags : 0 ,
259248 err : null ( ) ,
260249 } ;
261250
@@ -462,17 +451,23 @@ mod tests {
462451 #[ test]
463452 fn test_try_from_cstr_on_ok_fails ( ) {
464453 let status = ProfileStatus :: OK ;
465- let result: Result < & CStr , usize > = ( & status) . try_into ( ) ;
454+ let result: Result < & CStr , TryFromProfileStatusError > = ( & status) . try_into ( ) ;
466455 assert ! ( result. is_err( ) ) ;
467- assert_eq ! ( result. unwrap_err( ) , FLAG_OK ) ;
456+ assert ! ( matches!(
457+ result. unwrap_err( ) ,
458+ TryFromProfileStatusError :: Null
459+ ) ) ;
468460 }
469461
470462 #[ test]
471463 fn test_try_from_cstring_on_static_fails ( ) {
472464 let status = ProfileStatus :: from ( c"static" ) ;
473465 let result = CString :: try_from ( status) ;
474466 assert ! ( result. is_err( ) ) ;
475- assert_eq ! ( result. unwrap_err( ) , FLAG_STATIC ) ;
467+ assert ! ( matches!(
468+ result. unwrap_err( ) ,
469+ TryFromProfileStatusError :: IncorrectFlags ( FLAG_STATIC )
470+ ) ) ;
476471 }
477472
478473 #[ test]
0 commit comments