Skip to content

Commit cf34d04

Browse files
authored
refactor: simplify ProfileStatus encoding (#1416)
refactor: simplify ProfileStatus encoding fix: interpolation Co-authored-by: levi.morrison <[email protected]>
1 parent 379c66e commit cf34d04

File tree

3 files changed

+63
-68
lines changed

3 files changed

+63
-68
lines changed

examples/ffi/profiles.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ int main(void) {
2020
// Create a ProfilesDictionary for the new API
2121
ddog_prof_ProfilesDictionaryHandle dict = {0};
2222
ddog_prof_Status dict_status = ddog_prof_ProfilesDictionary_new(&dict);
23-
if (dict_status.flags != 0) {
23+
if (dict_status.err != NULL) {
2424
fprintf(stderr, "Failed to create dictionary: %s\n", dict_status.err);
2525
ddog_prof_Status_drop(&dict_status);
2626
exit(EXIT_FAILURE);
@@ -30,7 +30,7 @@ int main(void) {
3030
ddog_prof_Profile profile = {0};
3131
ddog_prof_Status profile_status =
3232
ddog_prof_Profile_with_dictionary(&profile, &dict, sample_types, &period);
33-
if (profile_status.flags != 0) {
33+
if (profile_status.err != NULL) {
3434
fprintf(stderr, "Failed to create profile: %s\n", profile_status.err);
3535
ddog_prof_Status_drop(&profile_status);
3636
ddog_prof_ProfilesDictionary_drop(&dict);
@@ -75,7 +75,7 @@ int main(void) {
7575

7676
dict_status = ddog_prof_ProfilesDictionary_insert_str(
7777
&function_name_id, dict, DDOG_CHARSLICE_C("{main}"), DDOG_PROF_UTF8_OPTION_ASSUME);
78-
if (dict_status.flags != 0) {
78+
if (dict_status.err != NULL) {
7979
fprintf(stderr, "Failed to insert function name: %s\n", dict_status.err);
8080
ddog_prof_Status_drop(&dict_status);
8181
goto cleanup;
@@ -84,15 +84,15 @@ int main(void) {
8484
dict_status = ddog_prof_ProfilesDictionary_insert_str(&filename_id, dict,
8585
DDOG_CHARSLICE_C("/srv/example/index.php"),
8686
DDOG_PROF_UTF8_OPTION_ASSUME);
87-
if (dict_status.flags != 0) {
87+
if (dict_status.err != NULL) {
8888
fprintf(stderr, "Failed to insert filename: %s\n", dict_status.err);
8989
ddog_prof_Status_drop(&dict_status);
9090
goto cleanup;
9191
}
9292

9393
dict_status = ddog_prof_ProfilesDictionary_insert_str(
9494
&label_key_id, dict, DDOG_CHARSLICE_C("unique_counter"), DDOG_PROF_UTF8_OPTION_ASSUME);
95-
if (dict_status.flags != 0) {
95+
if (dict_status.err != NULL) {
9696
fprintf(stderr, "Failed to insert label key: %s\n", dict_status.err);
9797
ddog_prof_Status_drop(&dict_status);
9898
goto cleanup;
@@ -107,7 +107,7 @@ int main(void) {
107107
};
108108

109109
dict_status = ddog_prof_ProfilesDictionary_insert_function(&function_id, dict, &function2);
110-
if (dict_status.flags != 0) {
110+
if (dict_status.err != NULL) {
111111
fprintf(stderr, "Failed to insert function: %s\n", dict_status.err);
112112
ddog_prof_Status_drop(&dict_status);
113113
goto cleanup;
@@ -138,7 +138,7 @@ int main(void) {
138138
label2.num = i;
139139

140140
ddog_prof_Status add2_status = ddog_prof_Profile_add2(&profile, sample2, 0);
141-
if (add2_status.flags != 0) {
141+
if (add2_status.err != NULL) {
142142
fprintf(stderr, "add2 error: %s\n", add2_status.err);
143143
ddog_prof_Status_drop(&add2_status);
144144
}

libdd-profiling-ffi/src/profile_error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ mod tests {
353353
let err = ProfileError::AllocError;
354354
let status = ProfileStatus::from(err);
355355

356-
assert_ne!(status.flags, 0); // Should be an error
356+
assert!(!status.err.is_null()); // Should be an error
357357
let cstr: &CStr = (&status).try_into().unwrap();
358358
assert!(cstr.to_str().unwrap().contains("memory allocator"));
359359

libdd-profiling-ffi/src/profile_status.rs

Lines changed: 55 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ use std::fmt::Display;
99
use std::mem::ManuallyDrop;
1010
use 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)]
3936
pub 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+
132139
impl 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

144153
impl 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

156165
impl 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

256245
impl 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

Comments
 (0)