Skip to content

Commit a35b8d1

Browse files
committed
FFI first pass
1 parent 3aafe24 commit a35b8d1

File tree

5 files changed

+337
-0
lines changed

5 files changed

+337
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

datadog-crashtracker-ffi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false }
4242
symbolic-demangle = { version = "12.8.0", default-features = false, features = ["rust", "cpp", "msvc"], optional = true }
4343
symbolic-common = { version = "12.8.0", default-features = false, optional = true }
4444
function_name = "0.3.0"
45+
tokio = { version = "1.0", features = ["rt", "rt-multi-thread"] }
4546
libc = "0.2.167"
4647
serde_json = "1.0.132"
4748
serde = { version = "1.0.214", features = ["derive"] }

datadog-crashtracker-ffi/src/crash_info/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod api;
55
mod builder;
66
mod metadata;
77
mod os_info;
8+
mod ping_info;
89
mod proc_info;
910
mod sig_info;
1011
mod span;
@@ -16,6 +17,7 @@ pub use api::*;
1617
pub use builder::*;
1718
pub use metadata::*;
1819
pub use os_info::*;
20+
pub use ping_info::*;
1921
pub use proc_info::*;
2022
pub use sig_info::*;
2123
pub use span::*;
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use super::{Metadata, SigInfo};
5+
use ::function_name::named;
6+
use datadog_crashtracker::{CrashPingBuilder, CrashPing};
7+
use ddcommon::Endpoint;
8+
use ddcommon_ffi::{
9+
slice::AsBytes, wrap_with_ffi_result, wrap_with_void_ffi_result, CharSlice, Error, Handle,
10+
ToInner, VoidResult,
11+
};
12+
13+
////////////////////////////////////////////////////////////////////////////////////////////////////
14+
// FFI API //
15+
////////////////////////////////////////////////////////////////////////////////////////////////////
16+
17+
#[allow(dead_code)]
18+
#[repr(C)]
19+
pub enum PingInfoBuilderNewResult {
20+
Ok(Handle<CrashPingBuilder>),
21+
Err(Error),
22+
}
23+
24+
/// Create a new CrashPingBuilder, and returns an opaque reference to it.
25+
/// # Safety
26+
/// No safety issues.
27+
#[no_mangle]
28+
#[must_use]
29+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_new() -> PingInfoBuilderNewResult {
30+
PingInfoBuilderNewResult::Ok(CrashPingBuilder::new().into())
31+
}
32+
33+
/// # Safety
34+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder
35+
/// made by this module, which has not previously been dropped.
36+
#[no_mangle]
37+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_drop(builder: *mut Handle<CrashPingBuilder>) {
38+
// Technically, this function has been designed so if it's double-dropped
39+
// then it's okay, but it's not something that should be relied on.
40+
if !builder.is_null() {
41+
drop((*builder).take())
42+
}
43+
}
44+
45+
#[allow(dead_code)]
46+
#[repr(C)]
47+
pub enum PingInfoNewResult {
48+
Ok(Handle<CrashPing>),
49+
Err(Error),
50+
}
51+
52+
/// # Safety
53+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder made by this module,
54+
/// which has not previously been dropped.
55+
#[no_mangle]
56+
#[must_use]
57+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_build(
58+
builder: *mut Handle<CrashPingBuilder>,
59+
) -> PingInfoNewResult {
60+
match ddog_crasht_ping_info_builder_build_impl(builder) {
61+
Ok(ping_info) => PingInfoNewResult::Ok(ping_info),
62+
Err(err) => PingInfoNewResult::Err(err.into()),
63+
}
64+
}
65+
66+
#[named]
67+
unsafe fn ddog_crasht_ping_info_builder_build_impl(
68+
mut builder: *mut Handle<CrashPingBuilder>,
69+
) -> anyhow::Result<Handle<CrashPing>> {
70+
wrap_with_ffi_result!({ anyhow::Ok(builder.take()?.build()?.into()) })
71+
}
72+
73+
/// # Safety
74+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder made by this module,
75+
/// which has not previously been dropped.
76+
/// The uuid CharSlice must be valid.
77+
#[no_mangle]
78+
#[must_use]
79+
#[named]
80+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_with_uuid(
81+
mut builder: *mut Handle<CrashPingBuilder>,
82+
uuid: CharSlice,
83+
) -> VoidResult {
84+
wrap_with_void_ffi_result!({
85+
// Take the builder, apply the method, and put it back
86+
let old_builder = builder.take()?;
87+
let new_builder = old_builder.with_crash_uuid(uuid.try_to_string()?);
88+
*builder = new_builder.into();
89+
})
90+
}
91+
92+
/// # Safety
93+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder made by this module,
94+
/// which has not previously been dropped.
95+
/// The metadata must be valid.
96+
#[no_mangle]
97+
#[must_use]
98+
#[named]
99+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_with_metadata(
100+
mut builder: *mut Handle<CrashPingBuilder>,
101+
metadata: Metadata,
102+
) -> VoidResult {
103+
wrap_with_void_ffi_result!({
104+
// Take the builder, apply the method, and put it back
105+
let old_builder = builder.take()?;
106+
let new_builder = old_builder.with_metadata(metadata.try_into()?);
107+
*builder = new_builder.into();
108+
})
109+
}
110+
111+
/// # Safety
112+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder made by this module,
113+
/// which has not previously been dropped.
114+
/// The sig_info must be valid.
115+
#[no_mangle]
116+
#[must_use]
117+
#[named]
118+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_with_sig_info(
119+
mut builder: *mut Handle<CrashPingBuilder>,
120+
sig_info: SigInfo,
121+
) -> VoidResult {
122+
wrap_with_void_ffi_result!({
123+
// Take the builder, apply the method, and put it back
124+
let old_builder = builder.take()?;
125+
let new_builder = old_builder.with_sig_info(sig_info.try_into()?);
126+
*builder = new_builder.into();
127+
})
128+
}
129+
130+
/// # Safety
131+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder made by this module,
132+
/// which has not previously been dropped.
133+
#[no_mangle]
134+
#[must_use]
135+
#[named]
136+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_with_os_info_this_machine(
137+
mut builder: *mut Handle<CrashPingBuilder>,
138+
) -> VoidResult {
139+
wrap_with_void_ffi_result!({
140+
// Take the builder, apply the method, and put it back
141+
let old_builder = builder.take()?;
142+
let new_builder = old_builder.with_os_info_this_machine();
143+
*builder = new_builder.into();
144+
})
145+
}
146+
147+
/// # Safety
148+
/// The `builder` can be null, but if non-null it must point to a PingInfoBuilder made by this module,
149+
/// which has not previously been dropped.
150+
#[no_mangle]
151+
#[must_use]
152+
#[named]
153+
pub unsafe extern "C" fn ddog_crasht_PingInfoBuilder_with_proc_info(
154+
mut builder: *mut Handle<CrashPingBuilder>,
155+
) -> VoidResult {
156+
wrap_with_void_ffi_result!({
157+
// Take the builder, apply the method, and put it back
158+
let old_builder = builder.take()?;
159+
let new_builder = old_builder.with_proc_info_this_process();
160+
*builder = new_builder.into();
161+
})
162+
}
163+
164+
/// # Safety
165+
/// The `ping_info` can be null, but if non-null it must point to a PingInfo
166+
/// made by this module, which has not previously been dropped.
167+
#[no_mangle]
168+
pub unsafe extern "C" fn ddog_crasht_PingInfo_drop(ping_info: *mut Handle<CrashPing>) {
169+
// Technically, this function has been designed so if it's double-dropped
170+
// then it's okay, but it's not something that should be relied on.
171+
if !ping_info.is_null() {
172+
drop((*ping_info).take())
173+
}
174+
}
175+
176+
/// # Safety
177+
/// The `ping_info` can be null, but if non-null it must point to a PingInfo made by this module,
178+
/// which has not previously been dropped.
179+
/// The endpoint can be null (uses builder's endpoint) or must be valid.
180+
#[no_mangle]
181+
#[must_use]
182+
#[named]
183+
pub unsafe extern "C" fn ddog_crasht_PingInfo_upload_to_endpoint(
184+
mut ping_info: *mut Handle<CrashPing>,
185+
endpoint: *const Endpoint,
186+
) -> VoidResult {
187+
wrap_with_void_ffi_result!({
188+
// Create a runtime to block on the async upload
189+
let rt = tokio::runtime::Builder::new_current_thread()
190+
.enable_all()
191+
.build()?;
192+
193+
// Take the ping_info and upload it
194+
let ping = ping_info.take()?;
195+
196+
// For now, we use the endpoint from the builder. In the future, we could
197+
// extend CrashPing to support endpoint override if needed.
198+
// The endpoint parameter is reserved for future use.
199+
if !endpoint.is_null() {
200+
// TODO: Consider supporting endpoint override during upload
201+
// For now, we ignore the endpoint parameter and use the builder's endpoint
202+
}
203+
204+
rt.block_on(ping.upload())?;
205+
})
206+
}
207+
208+
#[cfg(test)]
209+
mod tests {
210+
use super::*;
211+
use ddcommon_ffi::CharSlice;
212+
213+
#[test]
214+
#[cfg_attr(miri, ignore)]
215+
fn test_ping_info_builder_ffi_basic() {
216+
unsafe {
217+
// Create builder
218+
let builder_result = ddog_crasht_PingInfoBuilder_new();
219+
let mut builder = match builder_result {
220+
PingInfoBuilderNewResult::Ok(b) => b,
221+
PingInfoBuilderNewResult::Err(_) => panic!("Failed to create builder"),
222+
};
223+
224+
// Add UUID
225+
let uuid_slice = CharSlice::from("test-uuid-ffi-123");
226+
let result = ddog_crasht_PingInfoBuilder_with_uuid(&mut builder, uuid_slice);
227+
match result {
228+
ddcommon_ffi::VoidResult::Ok => {},
229+
ddcommon_ffi::VoidResult::Err(_) => panic!("Failed to set UUID"),
230+
}
231+
232+
// Add metadata
233+
let tags = ddcommon_ffi::Vec::new();
234+
235+
let metadata = Metadata {
236+
library_name: CharSlice::from("test-library"),
237+
library_version: CharSlice::from("1.0.0"),
238+
family: CharSlice::from("test"),
239+
tags: Some(&tags),
240+
};
241+
242+
let result = ddog_crasht_PingInfoBuilder_with_metadata(&mut builder, metadata);
243+
match result {
244+
ddcommon_ffi::VoidResult::Ok => {},
245+
ddcommon_ffi::VoidResult::Err(_) => panic!("Failed to set metadata"),
246+
}
247+
248+
// Add sig_info
249+
let sig_info = SigInfo {
250+
addr: CharSlice::from("0x0000000000001234"),
251+
code: 1,
252+
code_human_readable: datadog_crashtracker::SiCodes::SEGV_BNDERR,
253+
signo: 11,
254+
signo_human_readable: datadog_crashtracker::SignalNames::SIGSEGV,
255+
};
256+
257+
let result = ddog_crasht_PingInfoBuilder_with_sig_info(&mut builder, sig_info);
258+
match result {
259+
ddcommon_ffi::VoidResult::Ok => {},
260+
ddcommon_ffi::VoidResult::Err(_) => panic!("Failed to set sig_info"),
261+
}
262+
263+
// Add insights
264+
let result = ddog_crasht_PingInfoBuilder_with_os_info_this_machine(&mut builder);
265+
match result {
266+
ddcommon_ffi::VoidResult::Ok => {},
267+
ddcommon_ffi::VoidResult::Err(_) => panic!("Failed to set os_info"),
268+
}
269+
270+
let result = ddog_crasht_PingInfoBuilder_with_proc_info(&mut builder);
271+
match result {
272+
ddcommon_ffi::VoidResult::Ok => {},
273+
ddcommon_ffi::VoidResult::Err(_) => panic!("Failed to set proc_info"),
274+
}
275+
276+
// Build the ping
277+
let ping_result = ddog_crasht_PingInfoBuilder_build(&mut builder);
278+
let mut ping = match ping_result {
279+
PingInfoNewResult::Ok(p) => p,
280+
PingInfoNewResult::Err(_) => panic!("Failed to build ping info"),
281+
};
282+
283+
// Upload - for testing we'll pass null endpoint to use builder's endpoint
284+
let result = ddog_crasht_PingInfo_upload_to_endpoint(&mut ping, std::ptr::null());
285+
match result {
286+
ddcommon_ffi::VoidResult::Ok => {},
287+
ddcommon_ffi::VoidResult::Err(_) => {
288+
// Expected to fail since we don't have a real endpoint, that's okay
289+
}
290+
}
291+
292+
// Cleanup
293+
ddog_crasht_PingInfo_drop(&mut ping);
294+
ddog_crasht_PingInfoBuilder_drop(&mut builder);
295+
}
296+
}
297+
}

0 commit comments

Comments
 (0)