@@ -111,7 +111,6 @@ impl std::str::FromStr for CallbackType {
111
111
/// Global storage for the runtime callback
112
112
///
113
113
/// Uses atomic pointer to ensure safe access from signal handlers
114
- /// Stores enums directly - much simpler and signal-safe
115
114
static RUNTIME_CALLBACK : AtomicPtr < ( RuntimeStackCallback , RuntimeType , CallbackType ) > =
116
115
AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
117
116
@@ -146,8 +145,14 @@ pub struct RuntimeStackFrame {
146
145
/// - Only async-signal-safe functions
147
146
///
148
147
/// # Parameters
149
- /// - `emit_frame`: Function to call for each runtime frame
150
- /// - `emit_stacktrace_string`: Function to call for complete stacktrace string
148
+ /// - `emit_frame`: Function to call for each runtime frame (marked unsafe because it takes raw pointers)
149
+ /// - `emit_stacktrace_string`: Function to call for complete stacktrace string (marked unsafe because it takes raw C string pointers)
150
+ ///
151
+ /// # Safety
152
+ /// The callback function is marked unsafe because:
153
+ /// - It receives function pointers that take raw pointers as parameters
154
+ /// - The callback must ensure any pointers it passes to these functions are valid
155
+ /// - All C strings passed must be null-terminated and remain valid for the call duration
151
156
pub type RuntimeStackCallback = unsafe extern "C" fn (
152
157
emit_frame : unsafe extern "C" fn ( * const RuntimeStackFrame ) ,
153
158
emit_stacktrace_string : unsafe extern "C" fn ( * const c_char ) ,
@@ -213,6 +218,8 @@ pub fn register_runtime_stack_callback(
213
218
let previous = RUNTIME_CALLBACK . swap ( callback_data, Ordering :: SeqCst ) ;
214
219
215
220
if !previous. is_null ( ) {
221
+ // Safety: previous was returned by Box::into_raw() above or in a previous call,
222
+ // so it's guaranteed to be a valid Box pointer. We reconstruct the Box to drop it.
216
223
let _ = unsafe { Box :: from_raw ( previous) } ;
217
224
}
218
225
@@ -232,12 +239,19 @@ pub fn is_runtime_callback_registered() -> bool {
232
239
///
233
240
/// # Safety
234
241
/// This function loads from an atomic pointer and dereferences it.
242
+ /// The caller must ensure that no other thread is calling `clear_runtime_callback`
243
+ /// or `register_runtime_stack_callback` concurrently, as those could invalidate
244
+ /// the pointer between the null check and dereferencing.
235
245
pub unsafe fn get_registered_runtime_type_enum ( ) -> Option < RuntimeType > {
236
246
let callback_ptr = RUNTIME_CALLBACK . load ( Ordering :: SeqCst ) ;
237
247
if callback_ptr. is_null ( ) {
238
248
return None ;
239
249
}
240
250
251
+ // Safety: callback_ptr was checked to be non-null above, and was created by
252
+ // Box::into_raw() in register_runtime_stack_callback(), so it's a valid pointer
253
+ // to a properly aligned, initialized tuple. The atomic load with SeqCst ordering
254
+ // ensures we see the pointer after it was stored.
241
255
let ( _, runtime_type, _) = & * callback_ptr;
242
256
Some ( * runtime_type)
243
257
}
@@ -248,12 +262,19 @@ pub unsafe fn get_registered_runtime_type_enum() -> Option<RuntimeType> {
248
262
///
249
263
/// # Safety
250
264
/// This function loads from an atomic pointer and dereferences it.
265
+ /// The caller must ensure that no other thread is calling `clear_runtime_callback`
266
+ /// or `register_runtime_stack_callback` concurrently, as those could invalidate
267
+ /// the pointer between the null check and dereferencing.
251
268
pub unsafe fn get_registered_callback_type_enum ( ) -> Option < CallbackType > {
252
269
let callback_ptr = RUNTIME_CALLBACK . load ( Ordering :: SeqCst ) ;
253
270
if callback_ptr. is_null ( ) {
254
271
return None ;
255
272
}
256
273
274
+ // Safety: callback_ptr was checked to be non-null above, and was created by
275
+ // Box::into_raw() in register_runtime_stack_callback(), so it's a valid pointer
276
+ // to a properly aligned, initialized tuple. The atomic load with SeqCst ordering
277
+ // ensures we see the pointer after it was stored.
257
278
let ( _, _, callback_type) = & * callback_ptr;
258
279
Some ( * callback_type)
259
280
}
@@ -262,12 +283,19 @@ pub unsafe fn get_registered_callback_type_enum() -> Option<CallbackType> {
262
283
///
263
284
/// # Safety
264
285
/// This function loads from an atomic pointer and dereferences it.
286
+ /// The caller must ensure that no other thread is calling `clear_runtime_callback`
287
+ /// or `register_runtime_stack_callback` concurrently, as those could invalidate
288
+ /// the pointer between the null check and dereferencing.
265
289
pub unsafe fn get_registered_runtime_type_ptr ( ) -> * const std:: ffi:: c_char {
266
290
let callback_ptr = RUNTIME_CALLBACK . load ( Ordering :: SeqCst ) ;
267
291
if callback_ptr. is_null ( ) {
268
292
return std:: ptr:: null ( ) ;
269
293
}
270
294
295
+ // Safety: callback_ptr was checked to be non-null above, and was created by
296
+ // Box::into_raw() in register_runtime_stack_callback(), so it's a valid pointer
297
+ // to a properly aligned, initialized tuple. The returned C string pointer
298
+ // points to static string literals, so it's always valid.
271
299
let ( _, runtime_type, _) = & * callback_ptr;
272
300
runtime_type. as_cstr ( ) . as_ptr ( )
273
301
}
@@ -276,12 +304,19 @@ pub unsafe fn get_registered_runtime_type_ptr() -> *const std::ffi::c_char {
276
304
///
277
305
/// # Safety
278
306
/// This function loads from an atomic pointer and dereferences it.
307
+ /// The caller must ensure that no other thread is calling `clear_runtime_callback`
308
+ /// or `register_runtime_stack_callback` concurrently, as those could invalidate
309
+ /// the pointer between the null check and dereferencing.
279
310
pub unsafe fn get_registered_callback_type_ptr ( ) -> * const std:: ffi:: c_char {
280
311
let callback_ptr = RUNTIME_CALLBACK . load ( Ordering :: SeqCst ) ;
281
312
if callback_ptr. is_null ( ) {
282
313
return std:: ptr:: null ( ) ;
283
314
}
284
315
316
+ // Safety: callback_ptr was checked to be non-null above, and was created by
317
+ // Box::into_raw() in register_runtime_stack_callback(), so it's a valid pointer
318
+ // to a properly aligned, initialized tuple. The returned C string pointer
319
+ // points to static string literals, so it's always valid.
285
320
let ( _, _, callback_type) = & * callback_ptr;
286
321
callback_type. as_cstr ( ) . as_ptr ( )
287
322
}
@@ -294,11 +329,15 @@ pub unsafe fn get_registered_callback_type_ptr() -> *const std::ffi::c_char {
294
329
///
295
330
/// # Safety
296
331
/// This function should only be called when it's safe to clear the callback,
297
- /// such as during testing or application shutdown.
332
+ /// such as during testing or application shutdown. The caller must ensure:
333
+ /// - No other thread is concurrently calling functions that dereference the callback pointer
334
+ /// - No signal handlers are currently executing that might invoke the callback
335
+ /// - The callback is not being used in any other way
298
336
pub unsafe fn clear_runtime_callback ( ) {
299
337
let old_ptr = RUNTIME_CALLBACK . swap ( std:: ptr:: null_mut ( ) , Ordering :: SeqCst ) ;
300
338
if !old_ptr. is_null ( ) {
301
- // Drop the tuple to clean up
339
+ // Safety: old_ptr was created by Box::into_raw() in register_runtime_stack_callback(),
340
+ // so it's a valid Box pointer. We reconstruct the Box to properly drop the tuple.
302
341
let _ = Box :: from_raw ( old_ptr) ;
303
342
}
304
343
}
@@ -310,7 +349,10 @@ pub unsafe fn clear_runtime_callback() {
310
349
///
311
350
/// # Safety
312
351
/// This function is intended to be called from signal handlers and must maintain
313
- /// signal safety. It does not perform any dynamic allocation.
352
+ /// signal safety. It does not perform any dynamic allocation. The caller must ensure:
353
+ /// - No other thread is calling `clear_runtime_callback` concurrently
354
+ /// - The registered callback function is signal-safe
355
+ /// - The writer parameter remains valid for the duration of the call
314
356
#[ cfg( unix) ]
315
357
pub ( crate ) unsafe fn invoke_runtime_callback_with_writer < W : std:: io:: Write > (
316
358
writer : & mut W ,
@@ -320,16 +362,25 @@ pub(crate) unsafe fn invoke_runtime_callback_with_writer<W: std::io::Write>(
320
362
return Err ( std:: io:: Error :: other ( "No runtime callback registered" ) ) ;
321
363
}
322
364
365
+ // Safety: callback_ptr was checked to be non-null above, and was created by
366
+ // Box::into_raw() in register_runtime_stack_callback(), so it's a valid pointer
367
+ // to a properly aligned, initialized tuple.
323
368
let ( callback_fn, _, _) = & * callback_ptr;
324
369
325
370
// Reset frame counter
326
371
FRAME_COUNT . with ( |count| count. set ( 0 ) ) ;
327
372
328
373
// Define the emit_frame function that writes directly to the pipe
374
+ // Safety: This function is called by the user's runtime callback. It's marked unsafe
375
+ // because it dereferences raw pointers, but the safety is guaranteed by the contract
376
+ // with the runtime callback system.
329
377
unsafe extern "C" fn emit_frame_collector ( frame : * const RuntimeStackFrame ) {
330
378
// We need access to the writer, so we'll use thread-local storage for it
331
379
FRAME_WRITER . with ( |writer_cell| {
332
380
if let Some ( writer_ptr) = * writer_cell. borrow ( ) {
381
+ // Safety: writer_ptr was stored in thread-local storage before
382
+ // calling the user callback, so it's guaranteed to be valid and point
383
+ // to the same writer object we passed in.
333
384
let writer = & mut * writer_ptr;
334
385
335
386
FRAME_COUNT . with ( |count| {
@@ -341,6 +392,7 @@ pub(crate) unsafe fn invoke_runtime_callback_with_writer<W: std::io::Write>(
341
392
}
342
393
343
394
// Write the frame as JSON
395
+ // Safety: frame pointer is passed from the runtime callback, writer is valid
344
396
let _ = emit_frame_as_json ( writer, frame) ;
345
397
let _ = writer. flush ( ) ;
346
398
@@ -350,16 +402,22 @@ pub(crate) unsafe fn invoke_runtime_callback_with_writer<W: std::io::Write>(
350
402
} ) ;
351
403
}
352
404
405
+ // Safety: This function is called by the user's runtime callback with a C string pointer.
406
+ // The safety requirements are documented in the RuntimeStackCallback type.
353
407
unsafe extern "C" fn emit_stacktrace_string_collector ( stacktrace_string : * const c_char ) {
354
408
if stacktrace_string. is_null ( ) {
355
409
return ;
356
410
}
357
411
412
+ // Safety: stacktrace_string is guaranteed by the runtime callback contract to be
413
+ // a valid, null-terminated C string that remains valid for the duration of this call.
358
414
let cstr = std:: ffi:: CStr :: from_ptr ( stacktrace_string) ;
359
415
let bytes = cstr. to_bytes ( ) ;
360
416
361
417
FRAME_WRITER . with ( |writer_cell| {
362
418
if let Some ( writer_ptr) = * writer_cell. borrow ( ) {
419
+ // Safety: writer_ptr was stored in thread-local storage just before
420
+ // calling the user callback, so it's guaranteed to be valid.
363
421
let writer = & mut * writer_ptr;
364
422
let _ = writer. write_all ( bytes) ;
365
423
let _ = writer. flush ( ) ;
@@ -373,6 +431,8 @@ pub(crate) unsafe fn invoke_runtime_callback_with_writer<W: std::io::Write>(
373
431
} ) ;
374
432
375
433
// Invoke the user callback
434
+ // Safety: callback_fn was verified to be non-null during registration, and the
435
+ // emit functions we're passing are compatible with the expected function signatures.
376
436
callback_fn ( emit_frame_collector, emit_stacktrace_string_collector) ;
377
437
378
438
// Clear the writer reference
@@ -387,6 +447,11 @@ pub(crate) unsafe fn invoke_runtime_callback_with_writer<W: std::io::Write>(
387
447
///
388
448
/// This function writes a RuntimeStackFrame directly as JSON without intermediate allocation.
389
449
/// It must be signal-safe.
450
+ ///
451
+ /// # Safety
452
+ /// The caller must ensure that `frame` is either null or points to a valid, properly
453
+ /// initialized RuntimeStackFrame. All C string pointers within the frame must be either
454
+ /// null or point to valid, null-terminated C strings.
390
455
#[ cfg( unix) ]
391
456
unsafe fn emit_frame_as_json (
392
457
writer : & mut dyn std:: io:: Write ,
@@ -396,12 +461,16 @@ unsafe fn emit_frame_as_json(
396
461
return Ok ( ( ) ) ;
397
462
}
398
463
464
+ // Safety: frame was checked to be non-null above. The caller guarantees it points
465
+ // to a valid RuntimeStackFrame.
399
466
let frame_ref = & * frame;
400
467
write ! ( writer, "{{" ) ?;
401
468
let mut first = true ;
402
469
403
470
// Convert C strings to Rust strings and write JSON fields
404
471
if !frame_ref. function_name . is_null ( ) {
472
+ // Safety: frame_ref.function_name was checked to be non-null. The caller
473
+ // guarantees it points to a valid, null-terminated C string.
405
474
let c_str = std:: ffi:: CStr :: from_ptr ( frame_ref. function_name ) ;
406
475
if let Ok ( s) = c_str. to_str ( ) {
407
476
if !s. is_empty ( ) {
@@ -412,6 +481,8 @@ unsafe fn emit_frame_as_json(
412
481
}
413
482
414
483
if !frame_ref. file_name . is_null ( ) {
484
+ // Safety: frame_ref.file_name was checked to be non-null. The caller
485
+ // guarantees it points to a valid, null-terminated C string.
415
486
let c_str = std:: ffi:: CStr :: from_ptr ( frame_ref. file_name ) ;
416
487
if let Ok ( s) = c_str. to_str ( ) {
417
488
if !s. is_empty ( ) {
@@ -441,6 +512,8 @@ unsafe fn emit_frame_as_json(
441
512
}
442
513
443
514
if !frame_ref. class_name . is_null ( ) {
515
+ // Safety: frame_ref.class_name was checked to be non-null. The caller
516
+ // guarantees it points to a valid, null-terminated C string.
444
517
let c_str = std:: ffi:: CStr :: from_ptr ( frame_ref. class_name ) ;
445
518
if let Ok ( s) = c_str. to_str ( ) {
446
519
if !s. is_empty ( ) {
@@ -454,6 +527,8 @@ unsafe fn emit_frame_as_json(
454
527
}
455
528
456
529
if !frame_ref. module_name . is_null ( ) {
530
+ // Safety: frame_ref.module_name was checked to be non-null. The caller
531
+ // guarantees it points to a valid, null-terminated C string.
457
532
let c_str = std:: ffi:: CStr :: from_ptr ( frame_ref. module_name ) ;
458
533
if let Ok ( s) = c_str. to_str ( ) {
459
534
if !s. is_empty ( ) {
0 commit comments