diff --git a/libdd-common-ffi/src/slice.rs b/libdd-common-ffi/src/slice.rs index 58ecf96250..174c8727a1 100644 --- a/libdd-common-ffi/src/slice.rs +++ b/libdd-common-ffi/src/slice.rs @@ -222,11 +222,18 @@ impl<'a, T: 'a> Slice<'a, T> { /// /// 1. Fails if `self.ptr` is null and `self.len` is not zero. /// 2. Fails if `self.ptr` is not null and is unaligned. - /// 3. Fails if `self.len` is larger than [`isize::MAX`]. + /// 3. Fails if the total size in bytes (`self.len * size_of::()`) is larger than + /// [`isize::MAX`]. pub fn try_as_slice(&self) -> Result<&'a [T], SliceConversionError> { let (ptr, len) = self.as_raw_parts(); if !ptr.is_null() { - if len > isize::MAX as usize { + // `from_raw_parts` bounds the total size in *bytes*, not the element + // count: for a wide `T` a count within `isize::MAX` can still + // overflow. `checked_mul` also covers the overflow and ZST cases. + let too_large = len + .checked_mul(core::mem::size_of::()) + .is_none_or(|bytes| bytes > isize::MAX as usize); + if too_large { Err(SliceConversionError::LargeLength) } else if !ptr.is_aligned() { Err(SliceConversionError::MisalignedPointer) @@ -480,6 +487,25 @@ mod tests { )); } + #[test] + fn test_try_as_slice_large_byte_size() { + // `len` is within `isize::MAX` as an element count, but the byte size + // (`len * size_of::()`) exceeds it. The pointer is non-null and + // aligned, so the byte-size check is what must reject the slice. + let len = isize::MAX as usize / core::mem::size_of::() + 1; + let large_bytes: Slice = Slice { + ptr: ptr::NonNull::dangling().as_ptr(), + len, + _marker: PhantomData, + }; + + let result = large_bytes.try_as_slice(); + assert!(matches!( + result.unwrap_err(), + SliceConversionError::LargeLength + )); + } + #[test] fn test_try_as_slice_misaligned_pointer() { // Create a misaligned pointer for u64 by using a properly aligned diff --git a/libdd-common-ffi/src/slice_mut.rs b/libdd-common-ffi/src/slice_mut.rs index d87df1795b..309e295d5d 100644 --- a/libdd-common-ffi/src/slice_mut.rs +++ b/libdd-common-ffi/src/slice_mut.rs @@ -93,7 +93,13 @@ impl<'a, T: 'a> MutSlice<'a, T> { if let Some(ptr) = self.ptr { // Crashing immediately is likely better than ignoring these. assert!(ptr.is_aligned()); - assert!(self.len <= isize::MAX as usize); + // Total byte size, not element count, must fit in isize::MAX + // (from_raw_parts_mut). + let too_large = self + .len + .checked_mul(core::mem::size_of::()) + .is_none_or(|bytes| bytes > isize::MAX as usize); + assert!(!too_large); unsafe { slice::from_raw_parts_mut(ptr.as_ptr(), self.len) } } else { // Crashing immediately is likely better than ignoring this. @@ -117,11 +123,18 @@ impl<'a, T: 'a> MutSlice<'a, T> { /// instead. /// - Returns [`SliceConversionError::MisalignedPointer`] if the pointer is non-null and is not /// aligned correctly for the type. - /// - Returns [`SliceConversionError::LargeLength`] if the length of the slice exceeds - /// [`isize::MAX`]. + /// - Returns [`SliceConversionError::LargeLength`] if the total size in bytes (`self.len * + /// size_of::()`) exceeds [`isize::MAX`]. pub fn try_as_slice(&self) -> Result<&'a [T], SliceConversionError> { if let Some(ptr) = self.ptr { - if self.len > isize::MAX as usize { + // `from_raw_parts` bounds the total size in *bytes*, not the element + // count: for a wide `T` a count within `isize::MAX` can still + // overflow. `checked_mul` also covers the overflow and ZST cases. + let too_large = self + .len + .checked_mul(core::mem::size_of::()) + .is_none_or(|bytes| bytes > isize::MAX as usize); + if too_large { Err(SliceConversionError::LargeLength) } else if !ptr.is_aligned() { Err(SliceConversionError::MisalignedPointer) @@ -263,6 +276,19 @@ mod tests { _ = dangerous.as_mut_slice(); } + #[should_panic] + #[test] + fn test_long_byte_size_panic() { + // Element count within isize::MAX, byte size beyond it. + let len = isize::MAX as usize / core::mem::size_of::() + 1; + let mut dangerous: MutSlice = MutSlice { + ptr: Some(ptr::NonNull::dangling()), + len, + _marker: PhantomData, + }; + _ = dangerous.as_mut_slice(); + } + #[test] fn test_try_as_slice_success() { let mut data = vec![1u8, 2, 3, 4, 5]; @@ -314,6 +340,24 @@ mod tests { )); } + #[test] + fn test_try_as_slice_large_byte_size() { + // `len` is within `isize::MAX` as an element count, but the byte size + // (`len * size_of::()`) exceeds it. + let len = isize::MAX as usize / core::mem::size_of::() + 1; + let large_bytes: MutSlice = MutSlice { + ptr: Some(ptr::NonNull::dangling()), + len, + _marker: PhantomData, + }; + + let result = large_bytes.try_as_slice(); + assert!(matches!( + result.unwrap_err(), + SliceConversionError::LargeLength + )); + } + #[test] fn test_try_as_slice_misaligned_pointer() { // Create a misaligned pointer for u64 by using a properly aligned diff --git a/libdd-data-pipeline-ffi/cbindgen.toml b/libdd-data-pipeline-ffi/cbindgen.toml index 5e18df2f87..151095d02c 100644 --- a/libdd-data-pipeline-ffi/cbindgen.toml +++ b/libdd-data-pipeline-ffi/cbindgen.toml @@ -12,6 +12,7 @@ includes = ["common.h"] after_includes = """ typedef struct ddog_TraceExporter ddog_TraceExporter; typedef struct ddog_TracerSpan ddog_TracerSpan; +typedef struct ddog_TracerSpanEvent ddog_TracerSpanEvent; typedef struct ddog_TracerTraceChunks ddog_TracerTraceChunks; typedef struct ddog_TraceExporterCancelToken ddog_TraceExporterCancelToken; """ @@ -19,7 +20,7 @@ typedef struct ddog_TraceExporterCancelToken ddog_TraceExporterCancelToken; [export] prefix = "ddog_" renaming_overrides_prefixing = true -exclude = ["TraceExporter", "TracerSpan", "TracerTraceChunks", "TokioCancellationToken"] +exclude = ["TraceExporter", "TracerSpan", "TracerSpanEvent", "TracerTraceChunks", "TokioCancellationToken"] [export.rename] "ByteSlice" = "ddog_ByteSlice" @@ -31,6 +32,7 @@ exclude = ["TraceExporter", "TracerSpan", "TracerTraceChunks", "TokioCancellatio "ExporterErrorCode" = "ddog_TraceExporterErrorCode" "ExporterError" = "ddog_TraceExporterError" "TracerSpan" = "ddog_TracerSpan" +"TracerSpanEvent" = "ddog_TracerSpanEvent" "TracerSpanFields" = "ddog_TracerSpanFields" "TracerTraceChunks" = "ddog_TracerTraceChunks" "TokioCancellationToken" = "ddog_TraceExporterCancelToken" diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index 57f00cdfd3..54250bde13 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -13,36 +13,28 @@ use crate::error::{ExporterError, ExporterErrorCode as ErrorCode}; use crate::response::ExporterResponse; use crate::trace_exporter::TraceExporter; use crate::{catch_panic, gen_error}; -use libdd_common_ffi::slice::{AsBytes, ByteSlice}; +use libdd_common_ffi::slice::{AsBytes, ByteSlice, Slice}; use libdd_common_ffi::CharSlice; use libdd_tinybytes::{Bytes, BytesString}; -use libdd_trace_utils::span::v04::SpanBytes; +use libdd_trace_utils::span::v04::{ + AttributeAnyValueBytes, AttributeArrayValueBytes, SpanBytes, SpanEventBytes, +}; use std::ptr::NonNull; type TokioCancellationToken = tokio_util::sync::CancellationToken; -// --------------------------------------------------------------------------- -// Helper -// --------------------------------------------------------------------------- +#[inline] +fn slice_to_rust_slice<'a, T>(s: Slice<'a, T>) -> Result<&'a [T], Option>> { + s.try_as_slice() + .map_err(|_| gen_error!(ErrorCode::InvalidArgument)) +} -/// Convert a [`CharSlice`] to a [`BytesString`], copying the bytes. -/// -/// Returns [`ErrorCode::InvalidArgument`] if the slice is malformed and [`ErrorCode::InvalidInput`] -/// if the bytes are not valid UTF-8. #[inline] -fn charslice_to_bytesstring(s: CharSlice) -> Result> { - let bytes = s.try_as_bytes().map_err(|_| { - Box::new(ExporterError::new( - ErrorCode::InvalidArgument, - &ErrorCode::InvalidArgument.to_string(), - )) - })?; - BytesString::from_slice(bytes).map_err(|_| { - Box::new(ExporterError::new( - ErrorCode::InvalidInput, - &ErrorCode::InvalidInput.to_string(), - )) - }) +fn charslice_to_bytesstring(s: CharSlice) -> Result>> { + let bytes = s + .try_as_bytes() + .map_err(|_| gen_error!(ErrorCode::InvalidArgument))?; + BytesString::from_slice(bytes).map_err(|_| gen_error!(ErrorCode::InvalidInput)) } // --------------------------------------------------------------------------- @@ -52,6 +44,9 @@ fn charslice_to_bytesstring(s: CharSlice) -> Result`. pub struct TracerSpan(SpanBytes); +/// Opaque, owned span event under construction. +pub struct TracerSpanEvent(SpanEventBytes); + /// FFI-safe bundle of scalar fields for creating a [`TracerSpan`]. /// /// Passed by reference to [`ddog_tracer_span_new`] so that adding or @@ -93,7 +88,7 @@ pub unsafe extern "C" fn ddog_tracer_span_new( ) -> Option> { catch_panic!( if let Some(fields) = fields { - let inner = || -> Result<(), Box> { + let inner = || -> Result<(), Option>> { let service = charslice_to_bytesstring(fields.service)?; let name = charslice_to_bytesstring(fields.name)?; let resource = charslice_to_bytesstring(fields.resource)?; @@ -119,7 +114,7 @@ pub unsafe extern "C" fn ddog_tracer_span_new( out_handle.as_ptr().write(Box::new(TracerSpan(span))); Ok(()) }; - inner().err() + inner().err().flatten() } else { gen_error!(ErrorCode::InvalidArgument) }, @@ -136,7 +131,7 @@ pub unsafe extern "C" fn ddog_tracer_span_new( /// `handle` must have been created by [`ddog_tracer_span_new`] and must not /// be used after this call. #[no_mangle] -pub unsafe extern "C" fn ddog_tracer_span_free(handle: Box) { +pub unsafe extern "C" fn ddog_tracer_span_free(handle: Option>) { drop(handle); } @@ -161,11 +156,11 @@ pub unsafe extern "C" fn ddog_tracer_span_set_meta( if let Some(span) = handle { let key = match charslice_to_bytesstring(key) { Ok(s) => s, - Err(e) => return Some(e), + Err(e) => return e, }; let value = match charslice_to_bytesstring(value) { Ok(s) => s, - Err(e) => return Some(e), + Err(e) => return e, }; span.0.meta.insert(key, value); None @@ -197,7 +192,7 @@ pub unsafe extern "C" fn ddog_tracer_span_set_metric( if let Some(span) = handle { let key = match charslice_to_bytesstring(key) { Ok(s) => s, - Err(e) => return Some(e), + Err(e) => return e, }; span.0.metrics.insert(key, value); None @@ -230,7 +225,7 @@ pub unsafe extern "C" fn ddog_tracer_span_set_meta_struct_blob( if let Some(span) = handle { let key = match charslice_to_bytesstring(key) { Ok(s) => s, - Err(e) => return Some(e), + Err(e) => return e, }; let value = match value.try_as_bytes() { Ok(v) => v, @@ -247,6 +242,236 @@ pub unsafe extern "C" fn ddog_tracer_span_set_meta_struct_blob( ) } +// --------------------------------------------------------------------------- +// TracerSpanEvent +// --------------------------------------------------------------------------- + +/// Create an owned span event. +/// +/// The name is copied before this function returns. The event remains detached +/// until consumed by [`ddog_tracer_span_add_event`]. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_new( + out_handle: NonNull>, + name: CharSlice, + time_unix_nano: u64, +) -> Option> { + catch_panic!( + match charslice_to_bytesstring(name) { + Ok(name) => { + out_handle + .as_ptr() + .write(Box::new(TracerSpanEvent(SpanEventBytes { + time_unix_nano, + name, + ..Default::default() + }))); + None + } + Err(e) => e, + }, + gen_error!(ErrorCode::Panic) + ) +} + +/// Free a detached span event. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_free(handle: Option>) { + drop(handle); +} + +#[inline] +fn set_event_attribute( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + value: AttributeAnyValueBytes, +) -> Option> { + let Some(event) = event else { + return gen_error!(ErrorCode::InvalidArgument); + }; + let key = match charslice_to_bytesstring(key) { + Ok(key) => key, + Err(e) => return e, + }; + event.0.attributes.insert(key, value); + None +} + +fn set_event_array_attribute( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + values: Slice, + to_value: fn(T) -> AttributeArrayValueBytes, +) -> Option> { + slice_to_rust_slice(values) + .map(|values| { + set_event_attribute( + event, + key, + AttributeAnyValueBytes::Array(values.iter().copied().map(to_value).collect()), + ) + }) + .unwrap_or_else(|e| e) +} + +/// Add or overwrite a string event attribute. The key and value are copied. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_string( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + value: CharSlice, +) -> Option> { + catch_panic!( + match charslice_to_bytesstring(value) { + Ok(value) => set_event_attribute( + event, + key, + AttributeAnyValueBytes::SingleValue(AttributeArrayValueBytes::String(value)), + ), + Err(e) => e, + }, + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a boolean event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_bool( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + value: bool, +) -> Option> { + catch_panic!( + set_event_attribute( + event, + key, + AttributeAnyValueBytes::SingleValue(AttributeArrayValueBytes::Boolean(value)), + ), + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a signed integer event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_int( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + value: i64, +) -> Option> { + catch_panic!( + set_event_attribute( + event, + key, + AttributeAnyValueBytes::SingleValue(AttributeArrayValueBytes::Integer(value)), + ), + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a double event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_double( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + value: f64, +) -> Option> { + catch_panic!( + set_event_attribute( + event, + key, + AttributeAnyValueBytes::SingleValue(AttributeArrayValueBytes::Double(value)), + ), + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a homogeneous string array event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_string_array( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + values: Slice, +) -> Option> { + catch_panic!( + slice_to_rust_slice(values) + .and_then(|values| { + values + .iter() + .map(|value| { + charslice_to_bytesstring(*value).map(AttributeArrayValueBytes::String) + }) + .collect::, _>>() + }) + .map(|values| set_event_attribute(event, key, AttributeAnyValueBytes::Array(values))) + .unwrap_or_else(|e| e), + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a homogeneous boolean array event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_bool_array( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + values: Slice, +) -> Option> { + catch_panic!( + set_event_array_attribute(event, key, values, AttributeArrayValueBytes::Boolean), + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a homogeneous signed integer array event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_int_array( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + values: Slice, +) -> Option> { + catch_panic!( + set_event_array_attribute(event, key, values, AttributeArrayValueBytes::Integer), + gen_error!(ErrorCode::Panic) + ) +} + +/// Add or overwrite a homogeneous double array event attribute. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_event_set_double_array( + event: Option<&mut TracerSpanEvent>, + key: CharSlice, + values: Slice, +) -> Option> { + catch_panic!( + set_event_array_attribute(event, key, values, AttributeArrayValueBytes::Double), + gen_error!(ErrorCode::Panic) + ) +} + +/// Atomically attach a completed event to a span, consuming the event. +/// +/// A non-null `event` is always consumed — moved into the span on success, or +/// dropped on any error (a null `span`, or an internal panic). Callers must not +/// use or free the event after this call. On error the span is unchanged. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_add_event( + span: Option<&mut TracerSpan>, + event: Option>, +) -> Option> { + let Some(span) = span else { + return gen_error!(ErrorCode::InvalidArgument); + }; + let Some(event) = event else { + return gen_error!(ErrorCode::InvalidArgument); + }; + catch_panic!( + { + span.0.span_events.push(event.0); + None + }, + gen_error!(ErrorCode::Panic) + ) +} + // --------------------------------------------------------------------------- // TracerTraceChunks // --------------------------------------------------------------------------- @@ -289,7 +514,7 @@ pub unsafe extern "C" fn ddog_tracer_trace_chunks_new( /// /// `handle` must have been created by [`ddog_tracer_trace_chunks_new`]. #[no_mangle] -pub unsafe extern "C" fn ddog_tracer_trace_chunks_free(handle: Box) { +pub unsafe extern "C" fn ddog_tracer_trace_chunks_free(handle: Option>) { drop(handle); } @@ -478,6 +703,16 @@ mod tests { } } + fn make_event(name: &str, time_unix_nano: u64) -> Box { + unsafe { + let mut handle = MaybeUninit::>::uninit(); + let out = NonNull::new(handle.as_mut_ptr()).unwrap(); + let err = ddog_tracer_span_event_new(out, cs(name), time_unix_nano); + assert!(err.is_none()); + handle.assume_init() + } + } + #[test] fn new_sets_all_scalar_fields() { unsafe { @@ -517,7 +752,7 @@ mod tests { assert!(span.0.span_links.is_empty()); assert!(span.0.span_events.is_empty()); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -536,7 +771,7 @@ mod tests { assert_eq!(span.0.meta.get("http.method").unwrap().as_ref(), "GET"); assert_eq!(span.0.meta.get("http.url").unwrap().as_ref(), "/users"); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -552,7 +787,7 @@ mod tests { // tolerates duplicate entries. assert_eq!(span.0.meta.get("k").unwrap().as_ref(), "v2"); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -572,7 +807,7 @@ mod tests { assert_eq!(*span.0.metrics.get("_dd.measured").unwrap(), 1.0); assert_eq!(*span.0.metrics.get("_sampling_priority_v1").unwrap(), 2.0); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -588,7 +823,7 @@ mod tests { assert_eq!(span.0.meta_struct.get("_dd.stack").unwrap().as_ref(), value); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -605,7 +840,7 @@ mod tests { assert_eq!(span.0.meta_struct.get("k").unwrap().as_ref(), b"second"); assert_eq!(span.0.meta_struct.len(), 2); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -627,6 +862,434 @@ mod tests { } } + #[test] + fn event_attributes_preserve_types_and_arrays() { + unsafe { + let mut event = make_event("exception", 123); + assert!( + ddog_tracer_span_event_set_string(Some(&mut event), cs("string"), cs("value")) + .is_none() + ); + assert!(ddog_tracer_span_event_set_bool(Some(&mut event), cs("bool"), true).is_none()); + assert!(ddog_tracer_span_event_set_int(Some(&mut event), cs("int"), -42).is_none()); + assert!( + ddog_tracer_span_event_set_double(Some(&mut event), cs("double"), 1.5).is_none() + ); + + let strings = [cs("one"), cs("two")]; + let bools = [true, false]; + let ints = [-1, 2]; + let doubles = [1.25, 2.5]; + assert!(ddog_tracer_span_event_set_string_array( + Some(&mut event), + cs("strings"), + Slice::from(&strings[..]) + ) + .is_none()); + assert!(ddog_tracer_span_event_set_bool_array( + Some(&mut event), + cs("bools"), + Slice::from(&bools[..]) + ) + .is_none()); + assert!(ddog_tracer_span_event_set_int_array( + Some(&mut event), + cs("ints"), + Slice::from(&ints[..]) + ) + .is_none()); + assert!(ddog_tracer_span_event_set_double_array( + Some(&mut event), + cs("doubles"), + Slice::from(&doubles[..]) + ) + .is_none()); + + assert_eq!(event.0.name.as_ref(), "exception"); + assert_eq!(event.0.time_unix_nano, 123); + assert_eq!( + event.0.attributes.get("string"), + Some(&AttributeAnyValueBytes::SingleValue( + AttributeArrayValueBytes::String(BytesString::from("value")) + )) + ); + assert_eq!( + event.0.attributes.get("bool"), + Some(&AttributeAnyValueBytes::SingleValue( + AttributeArrayValueBytes::Boolean(true) + )) + ); + assert_eq!( + event.0.attributes.get("int"), + Some(&AttributeAnyValueBytes::SingleValue( + AttributeArrayValueBytes::Integer(-42) + )) + ); + assert_eq!( + event.0.attributes.get("double"), + Some(&AttributeAnyValueBytes::SingleValue( + AttributeArrayValueBytes::Double(1.5) + )) + ); + assert_eq!( + event.0.attributes.get("strings"), + Some(&AttributeAnyValueBytes::Array(vec![ + AttributeArrayValueBytes::String(BytesString::from("one")), + AttributeArrayValueBytes::String(BytesString::from("two")), + ])) + ); + assert_eq!( + event.0.attributes.get("bools"), + Some(&AttributeAnyValueBytes::Array(vec![ + AttributeArrayValueBytes::Boolean(true), + AttributeArrayValueBytes::Boolean(false), + ])) + ); + assert_eq!( + event.0.attributes.get("ints"), + Some(&AttributeAnyValueBytes::Array(vec![ + AttributeArrayValueBytes::Integer(-1), + AttributeArrayValueBytes::Integer(2), + ])) + ); + assert_eq!( + event.0.attributes.get("doubles"), + Some(&AttributeAnyValueBytes::Array(vec![ + AttributeArrayValueBytes::Double(1.25), + AttributeArrayValueBytes::Double(2.5), + ])) + ); + + ddog_tracer_span_event_free(Some(event)); + } + } + + #[test] + fn event_attributes_accept_empty_typed_arrays() { + unsafe { + let mut event = make_event("empty-arrays", 123); + let strings: [CharSlice<'_>; 0] = []; + let bools: [bool; 0] = []; + let ints: [i64; 0] = []; + let doubles: [f64; 0] = []; + + assert!(ddog_tracer_span_event_set_string_array( + Some(&mut event), + cs("strings"), + Slice::from(&strings[..]) + ) + .is_none()); + assert!(ddog_tracer_span_event_set_bool_array( + Some(&mut event), + cs("bools"), + Slice::from(&bools[..]) + ) + .is_none()); + assert!(ddog_tracer_span_event_set_int_array( + Some(&mut event), + cs("ints"), + Slice::from(&ints[..]) + ) + .is_none()); + assert!(ddog_tracer_span_event_set_double_array( + Some(&mut event), + cs("doubles"), + Slice::from(&doubles[..]) + ) + .is_none()); + + for key in ["strings", "bools", "ints", "doubles"] { + assert_eq!( + event.0.attributes.get(key), + Some(&AttributeAnyValueBytes::Array(Vec::new())) + ); + } + + ddog_tracer_span_event_free(Some(event)); + } + } + + #[test] + fn invalid_event_strings_do_not_partially_update_the_event() { + unsafe { + let mut event = make_event("invalid", 123); + let invalid = CharSlice::from_bytes(&[0xff]); + let err = ddog_tracer_span_event_set_string(Some(&mut event), cs("scalar"), invalid); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + assert!(event.0.attributes.is_empty()); + + let values = [cs("valid"), invalid]; + let err = ddog_tracer_span_event_set_string_array( + Some(&mut event), + cs("array"), + Slice::from(&values[..]), + ); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + assert!(event.0.attributes.is_empty()); + + ddog_tracer_span_event_free(Some(event)); + } + } + + #[test] + fn malformed_event_arrays_report_invalid_argument() { + unsafe { + let mut event = make_event("malformed", 123); + + // Null pointer + let strings = Slice::from_raw_parts(std::ptr::null::(), 2); + let bools = Slice::from_raw_parts(std::ptr::null::(), 2); + let ints = Slice::from_raw_parts(std::ptr::null::(), 2); + let doubles = Slice::from_raw_parts(std::ptr::null::(), 2); + + for err in [ + ddog_tracer_span_event_set_string_array(Some(&mut event), cs("strings"), strings), + ddog_tracer_span_event_set_bool_array(Some(&mut event), cs("bools"), bools), + ddog_tracer_span_event_set_int_array(Some(&mut event), cs("ints"), ints), + ddog_tracer_span_event_set_double_array(Some(&mut event), cs("doubles"), doubles), + ] { + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + } + assert!(event.0.attributes.is_empty()); + + // Misaligned pointer + let buf = [0u8; 32]; + let misaligned = buf.as_ptr().add(1).cast::(); + assert!(!misaligned.is_aligned()); + let err = ddog_tracer_span_event_set_int_array( + Some(&mut event), + cs("ints"), + Slice::from_raw_parts(misaligned, 2), + ); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + assert!(event.0.attributes.is_empty()); + + // A malformed element + let elements = [cs("valid"), Slice::from_raw_parts(std::ptr::null(), 1)]; + let err = ddog_tracer_span_event_set_string_array( + Some(&mut event), + cs("strings"), + Slice::from(&elements[..]), + ); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + assert!(event.0.attributes.is_empty()); + + // A malformed key + let err = ddog_tracer_span_event_set_bool( + Some(&mut event), + Slice::from_raw_parts(std::ptr::null(), 1), + true, + ); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + assert!(event.0.attributes.is_empty()); + + // A length beyond `isize::MAX`. + let one = [1i64]; + let err = ddog_tracer_span_event_set_int_array( + Some(&mut event), + cs("ints"), + Slice::from_raw_parts(one.as_ptr(), usize::MAX), + ); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + assert!(event.0.attributes.is_empty()); + + ddog_tracer_span_event_free(Some(event)); + } + } + + #[test] + fn event_setters_reject_a_null_event() { + unsafe { + let strings = [cs("one")]; + let bools = [true]; + let ints = [1i64]; + let doubles = [1.0f64]; + + for err in [ + ddog_tracer_span_event_set_string(None, cs("k"), cs("v")), + ddog_tracer_span_event_set_bool(None, cs("k"), true), + ddog_tracer_span_event_set_int(None, cs("k"), 1), + ddog_tracer_span_event_set_double(None, cs("k"), 1.0), + ddog_tracer_span_event_set_string_array(None, cs("k"), Slice::from(&strings[..])), + ddog_tracer_span_event_set_bool_array(None, cs("k"), Slice::from(&bools[..])), + ddog_tracer_span_event_set_int_array(None, cs("k"), Slice::from(&ints[..])), + ddog_tracer_span_event_set_double_array(None, cs("k"), Slice::from(&doubles[..])), + ] { + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + } + } + } + + #[test] + fn invalid_utf8_event_keys_report_invalid_input() { + unsafe { + let mut event = make_event("keys", 1); + let invalid = CharSlice::from_bytes(&[0xff]); + let strings = [cs("one")]; + let bools = [true]; + let ints = [1i64]; + let doubles = [1.0f64]; + + for err in [ + ddog_tracer_span_event_set_string(Some(&mut event), invalid, cs("v")), + ddog_tracer_span_event_set_bool(Some(&mut event), invalid, true), + ddog_tracer_span_event_set_int(Some(&mut event), invalid, 1), + ddog_tracer_span_event_set_double(Some(&mut event), invalid, 1.0), + ddog_tracer_span_event_set_string_array( + Some(&mut event), + invalid, + Slice::from(&strings[..]), + ), + ddog_tracer_span_event_set_bool_array( + Some(&mut event), + invalid, + Slice::from(&bools[..]), + ), + ddog_tracer_span_event_set_int_array( + Some(&mut event), + invalid, + Slice::from(&ints[..]), + ), + ddog_tracer_span_event_set_double_array( + Some(&mut event), + invalid, + Slice::from(&doubles[..]), + ), + ] { + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + } + assert!(event.0.attributes.is_empty()); + + ddog_tracer_span_event_free(Some(event)); + } + } + + #[test] + fn event_new_rejects_invalid_names() { + unsafe { + // Invalid UTF-8 contents. + let mut handle = MaybeUninit::>::uninit(); + let out = NonNull::new(handle.as_mut_ptr()).unwrap(); + let err = ddog_tracer_span_event_new(out, CharSlice::from_bytes(&[0xff]), 1); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + + // A malformed name slice, rejected before any UTF-8 check. + let mut handle = MaybeUninit::>::uninit(); + let out = NonNull::new(handle.as_mut_ptr()).unwrap(); + let err = + ddog_tracer_span_event_new(out, Slice::from_raw_parts(std::ptr::null(), 1), 1); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + } + } + + #[test] + fn event_new_accepts_an_empty_name() { + unsafe { + let event = make_event("", 0); + assert_eq!(event.0.name.as_ref(), ""); + assert_eq!(event.0.time_unix_nano, 0); + assert!(event.0.attributes.is_empty()); + ddog_tracer_span_event_free(Some(event)); + } + } + + #[test] + fn event_free_accepts_null() { + unsafe { + ddog_tracer_span_event_free(None); + } + } + + #[test] + fn add_event_rejects_a_null_event() { + unsafe { + let mut span = make_minimal_span(); + let err = ddog_tracer_span_add_event(Some(&mut span), None); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); + ddog_trace_exporter_error_free(err); + assert!(span.0.span_events.is_empty()); + ddog_tracer_span_free(Some(span)); + } + } + + #[test] + fn attached_events_keep_their_attributes() { + unsafe { + let mut span = make_minimal_span(); + let mut event = make_event("exception", 42); + assert!( + ddog_tracer_span_event_set_string(Some(&mut event), cs("message"), cs("boom")) + .is_none() + ); + let codes = [1i64, 2]; + assert!(ddog_tracer_span_event_set_int_array( + Some(&mut event), + cs("codes"), + Slice::from(&codes[..]) + ) + .is_none()); + + assert!(ddog_tracer_span_add_event(Some(&mut span), Some(event)).is_none()); + + assert_eq!(span.0.span_events.len(), 1); + let attached = &span.0.span_events[0]; + assert_eq!(attached.name.as_ref(), "exception"); + assert_eq!(attached.time_unix_nano, 42); + assert_eq!(attached.attributes.len(), 2); + assert_eq!( + attached.attributes.get("message"), + Some(&AttributeAnyValueBytes::SingleValue( + AttributeArrayValueBytes::String(BytesString::from("boom")) + )) + ); + assert_eq!( + attached.attributes.get("codes"), + Some(&AttributeAnyValueBytes::Array(vec![ + AttributeArrayValueBytes::Integer(1), + AttributeArrayValueBytes::Integer(2), + ])) + ); + + ddog_tracer_span_free(Some(span)); + } + } + + #[test] + fn attaching_events_is_atomic_and_preserves_order() { + unsafe { + let mut span = make_minimal_span(); + let first = make_event("first", 10); + let second = make_event("second", 20); + + assert!(ddog_tracer_span_add_event(Some(&mut span), Some(first)).is_none()); + assert!(ddog_tracer_span_add_event(Some(&mut span), Some(second)).is_none()); + assert_eq!(span.0.span_events.len(), 2); + assert_eq!(span.0.span_events[0].name.as_ref(), "first"); + assert_eq!(span.0.span_events[0].time_unix_nano, 10); + assert_eq!(span.0.span_events[1].name.as_ref(), "second"); + assert_eq!(span.0.span_events[1].time_unix_nano, 20); + + let detached = make_event("detached", 30); + let err = ddog_tracer_span_add_event(None, Some(detached)); + assert!(err.is_some()); + ddog_trace_exporter_error_free(err); + assert_eq!(span.0.span_events.len(), 2); + + ddog_tracer_span_free(Some(span)); + } + } + #[test] fn set_meta_struct_blob_null_handle_returns_error() { unsafe { @@ -647,7 +1310,7 @@ mod tests { assert!(span.0.meta_struct.is_empty()); ddog_trace_exporter_error_free(err); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -662,7 +1325,7 @@ mod tests { assert!(span.0.meta_struct.is_empty()); ddog_trace_exporter_error_free(err); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -677,7 +1340,7 @@ mod tests { assert!(span.0.meta_struct.is_empty()); ddog_trace_exporter_error_free(err); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -690,7 +1353,7 @@ mod tests { assert!(err.is_none()); assert_eq!(span.0.meta_struct.get("k").unwrap().as_ref(), b""); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -704,7 +1367,7 @@ mod tests { assert!(err.is_none()); assert_eq!(span.0.meta_struct.get("").unwrap().as_ref(), b"value"); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -719,7 +1382,7 @@ mod tests { assert!(span.0.meta.is_empty()); ddog_trace_exporter_error_free(err); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -749,7 +1412,7 @@ mod tests { assert_eq!(span.0.name.as_ref(), ""); assert_eq!(span.0.service.as_ref(), ""); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -793,7 +1456,7 @@ mod tests { assert_eq!(chunks.0[0].len(), 2); assert_eq!(chunks.0[1].len(), 1); - ddog_tracer_trace_chunks_free(chunks); + ddog_tracer_trace_chunks_free(Some(chunks)); } } @@ -817,7 +1480,7 @@ mod tests { assert!(err.is_some()); ddog_trace_exporter_error_free(err); - ddog_tracer_trace_chunks_free(chunks); + ddog_tracer_trace_chunks_free(Some(chunks)); } } @@ -832,7 +1495,7 @@ mod tests { assert!(err.is_some()); ddog_trace_exporter_error_free(err); - ddog_tracer_trace_chunks_free(chunks); + ddog_tracer_trace_chunks_free(Some(chunks)); } } @@ -851,7 +1514,7 @@ mod tests { unsafe { let chunks = make_chunks(0); assert_eq!(chunks.0.len(), 0); - ddog_tracer_trace_chunks_free(chunks); + ddog_tracer_trace_chunks_free(Some(chunks)); } } @@ -865,7 +1528,7 @@ mod tests { assert_eq!(chunks.0.len(), 1); assert_eq!(chunks.0[0].len(), 0); - ddog_tracer_trace_chunks_free(chunks); + ddog_tracer_trace_chunks_free(Some(chunks)); } } @@ -918,7 +1581,7 @@ mod tests { assert!(err.is_some()); assert_eq!(err.as_ref().unwrap().code, ErrorCode::Panic); ddog_trace_exporter_error_free(err); - ddog_tracer_trace_chunks_free(chunks); + ddog_tracer_trace_chunks_free(Some(chunks)); } }