@@ -24,11 +24,30 @@ use std::ffi::c_void;
2424use libdd_profiling_heap_sampler:: { dd_sample_flag_peek, dd_tl_state_get_or_init} ;
2525use serial_test:: serial;
2626
27+ /// Warn (once) when these tests aren't running under nextest. The GOT install
28+ /// is permanent, so isolation depends on each test getting its own process;
29+ /// nextest sets `NEXTEST=1`, plain `cargo test` shares one process and leaks
30+ /// a prior test's install into later ones.
31+ fn warn_if_not_isolated ( ) {
32+ use std:: sync:: Once ;
33+ static ONCE : Once = Once :: new ( ) ;
34+ ONCE . call_once ( || {
35+ if std:: env:: var_os ( "NEXTEST" ) . is_none ( ) {
36+ eprintln ! (
37+ "warning: heap-gotter install tests need per-test process isolation; \
38+ run with `cargo nextest run`. Under `cargo test` a prior test's \
39+ permanent GOT install leaks into later ones."
40+ ) ;
41+ }
42+ } ) ;
43+ }
44+
2745/// After install the heap should still be functional and no recursive
2846/// crash should occur when malloc/free go through the patched GOT.
2947#[ test]
3048#[ serial]
3149fn install_keeps_heap_functional ( ) {
50+ warn_if_not_isolated ( ) ;
3251 extern "C" {
3352 fn malloc ( size : usize ) -> * mut c_void ;
3453 }
@@ -40,9 +59,17 @@ fn install_keeps_heap_functional() {
4059 ) ;
4160
4261 unsafe {
43- let p = malloc ( 64 ) ;
62+ let p = malloc ( 64 ) as * mut u8 ;
4463 assert ! ( !p. is_null( ) , "malloc returned NULL post-install" ) ;
45- libc:: free ( p) ;
64+ // Write then read back the whole buffer to prove it's real, usable
65+ // memory, not just a non-NULL (possibly mis-offset) pointer.
66+ for i in 0 ..64 {
67+ p. add ( i) . write ( ( i as u8 ) ^ 0xA5 ) ;
68+ }
69+ for i in 0 ..64 {
70+ assert_eq ! ( p. add( i) . read( ) , ( i as u8 ) ^ 0xA5 , "byte {i} corrupted" ) ;
71+ }
72+ libc:: free ( p as * mut c_void ) ;
4673 }
4774}
4875
@@ -54,6 +81,7 @@ fn install_keeps_heap_functional() {
5481#[ test]
5582#[ serial]
5683fn install_produces_sampled_allocations ( ) {
84+ warn_if_not_isolated ( ) ;
5785 let installed = libdd_profiling_heap_gotter:: install_heap_overrides ( ) ;
5886 assert ! ( installed) ;
5987
@@ -97,6 +125,7 @@ fn install_produces_sampled_allocations() {
97125#[ test]
98126#[ serial]
99127fn realloc_null_produces_sampled_allocation ( ) {
128+ warn_if_not_isolated ( ) ;
100129 let installed = libdd_profiling_heap_gotter:: install_heap_overrides ( ) ;
101130 assert ! ( installed) ;
102131
@@ -130,6 +159,7 @@ fn realloc_null_produces_sampled_allocation() {
130159#[ test]
131160#[ serial]
132161fn page_aligned_allocations_are_unsampled ( ) {
162+ warn_if_not_isolated ( ) ;
133163 let installed = libdd_profiling_heap_gotter:: install_heap_overrides ( ) ;
134164 assert ! ( installed) ;
135165
@@ -162,6 +192,7 @@ fn page_aligned_allocations_are_unsampled() {
162192#[ test]
163193#[ serial]
164194fn realloc_of_sampled_allocation_preserves_data ( ) {
195+ warn_if_not_isolated ( ) ;
165196 let installed = libdd_profiling_heap_gotter:: install_heap_overrides ( ) ;
166197 assert ! ( installed) ;
167198
@@ -246,6 +277,7 @@ unsafe fn alloc_aligned(align: usize, size: usize) -> *mut c_void {
246277#[ test]
247278#[ serial]
248279fn realloc_stress_across_alignments_preserves_data ( ) {
280+ warn_if_not_isolated ( ) ;
249281 // Mirrors the demo's menu, plus 2048 to bracket the 1024 cap on both
250282 // sides. Small alignments sample; those above the cap pass through.
251283 const ALIGNMENTS : & [ usize ] = & [ 1 , 8 , 16 , 32 , 64 , 128 , 256 , 512 , 1024 , 2048 , 4096 , 8192 ] ;
@@ -336,3 +368,36 @@ fn realloc_stress_across_alignments_preserves_data() {
336368 #[ cfg( not( feature = "live-heap" ) ) ]
337369 let _ = saw_sampled;
338370}
371+
372+ /// A failing allocation must surface the real allocator's error/errno through
373+ /// our hooks unchanged. Avoids relying on `aligned_alloc` rejecting a bad
374+ /// alignment, since older glibc (e.g. CentOS 7) doesn't validate that.
375+ #[ test]
376+ #[ serial]
377+ fn invalid_alignment_passes_through_error ( ) {
378+ warn_if_not_isolated ( ) ;
379+ let installed = libdd_profiling_heap_gotter:: install_heap_overrides ( ) ;
380+ assert ! ( installed) ;
381+
382+ unsafe {
383+ // posix_memalign with a non-power-of-two alignment must return EINVAL.
384+ // POSIX-specified, so reliable across glibc versions; exercises our
385+ // hook forwarding the real return code unchanged.
386+ let mut out: * mut c_void = std:: ptr:: null_mut ( ) ;
387+ let rc = libc:: posix_memalign ( & mut out, 3 , 64 ) ;
388+ assert_eq ! ( rc, libc:: EINVAL , "posix_memalign should return EINVAL" ) ;
389+
390+ // A failing allocation must preserve the real allocator's errno through
391+ // our post-call bookkeeping. A huge aligned_alloc reliably fails with
392+ // ENOMEM on every libc (size is a multiple of the alignment).
393+ * libc:: __errno_location ( ) = 0 ;
394+ let huge = usize:: MAX & !0xfff ;
395+ let p = libc:: aligned_alloc ( 16 , huge) ;
396+ assert ! ( p. is_null( ) , "huge aligned_alloc should fail" ) ;
397+ assert_eq ! (
398+ std:: io:: Error :: last_os_error( ) . raw_os_error( ) ,
399+ Some ( libc:: ENOMEM ) ,
400+ "errno must survive our post-call bookkeeping"
401+ ) ;
402+ }
403+ }
0 commit comments