Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asan finds memory leaks. #524

Closed
vext01 opened this issue Feb 21, 2024 · 16 comments · Fixed by #528
Closed

Asan finds memory leaks. #524

vext01 opened this issue Feb 21, 2024 · 16 comments · Fixed by #528

Comments

@vext01
Copy link

vext01 commented Feb 21, 2024

I noticed this when my project's CI failed after using iced_x86 for the first time.

If you run the Rust test suite with Asan enabled, there are a lot of leaks reported. e.g.:

$ ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-15 RUSTFLAGS="-Z sanitizer=address" cargo test --lib -Z build-std --target x86_64-unknown-linux-gnu
...
Direct leak of 7608 byte(s) in 317 object(s) allocated from:                                                                                                                                                                                  
    #0 0x564d04b1c0ef in malloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:68:3                                                                                                                                  
    #1 0x564d0643799b in std::sys::pal::unix::alloc::_$LT$impl$u20$core..alloc..global..GlobalAlloc$u20$for$u20$std..alloc..System$GT$::alloc::h06dd0a20178c9edd /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/
src/rust/library/std/src/sys/pal/unix/alloc.rs:14:13                                                                                                                                                                                          
    #2 0x564d065e833c in __rdl_alloc /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/alloc.rs:394:13                                                                                    
    #3 0x564d05d989e4 in alloc::alloc::Global::alloc_impl::h07a2feba7ca619c8 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/alloc.rs:181:73                                          
    #4 0x564d05d99658 in _$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::h61e7f0c3f82e9586 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/alloc.rs:241:9  
    #5 0x564d059717c5 in _$LT$iced_x86..formatter..fast..fmt_tbl..FMT_DATA$u20$as$u20$core..ops..deref..Deref$GT$::deref::__static_ref_initialize::hf4d827215169b2b4 /home/vext01/source/iced/src/rust/iced-x86/src/formatter/fast/fmt_tbl.rs:
20:49                                                                                                                                                                                                                                         
    #6 0x564d059717c5 in core::ops::function::FnOnce::call_once::hfa5fc15e22c577d4 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5                               
    #7 0x564d05f6d045 in std::sync::once::Once::call_once::he6f6c46da8d6d107 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sync/once.rs:149:9                                         
    #8 0x564d059c99a7 in lazy_static::lazy::Lazy$LT$T$GT$::get::h0abcb01fa137bc7b /home/vext01/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lazy_static-1.4.0/src/inline_lazy.rs:30:9                                                 
    #9 0x564d059c99a7 in _$LT$iced_x86..formatter..fast..fmt_tbl..FMT_DATA$u20$as$u20$core..ops..deref..Deref$GT$::deref::__stability::ha0ac771135e86dd9 /home/vext01/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lazy_static-1.4.0/s
rc/lib.rs:142:21                                                                                                                                                                                                                              
    #10 0x564d059c99a7 in _$LT$iced_x86..formatter..fast..fmt_tbl..FMT_DATA$u20$as$u20$core..ops..deref..Deref$GT$::deref::h226c645a2243c5aa /home/vext01/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lazy_static-1.4.0/src/lib.rs:14
4:17                                                                                                                                                                                                                                          
    #11 0x564d04ccd89e in iced_x86::formatter::fast::SpecializedFormatter$LT$TraitOptions$GT$::try_with_options::h4e5e9d568b60c51b /home/vext01/source/iced/src/rust/iced-x86/src/formatter/fast.rs:776:23                                    
    #12 0x564d04cc6e31 in iced_x86::formatter::fast::SpecializedFormatter$LT$TraitOptions$GT$::new::h3f80414dfbfcc847 /home/vext01/source/iced/src/rust/iced-x86/src/formatter/fast.rs:753:3                                                  
    #13 0x564d05d58149 in iced_x86::formatter::fast::tests::format_hex2::h7cc6a557a4ee1585 /home/vext01/source/iced/src/rust/iced-x86/src/formatter/fast/tests/mod.rs:128:27                                                                  
    #14 0x564d05bd0106 in iced_x86::formatter::fast::tests::format_hex2::_$u7b$$u7b$closure$u7d$$u7d$::h256851816dbd80ad /home/vext01/source/iced/src/rust/iced-x86/src/formatter/fast/tests/mod.rs:109:17                                    
    #15 0x564d0632ebea in test::types::RunnableTest::run::h36218d6a648d1201 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/types.rs:146:40                                            
    #16 0x564d062bfbf2 in test::run_test_in_process::_$u7b$$u7b$closure$u7d$$u7d$::hb227ced748144f42 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:643:60                     
    #17 0x564d06290b72 in _$LT$core..panic..unwind_safe..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h68f075c3bc59fa64 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/l
ib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:272:9                                                                                                                                                                               
    #18 0x564d062402be in std::panicking::try::do_call::h09095b57b4461875 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:555:40                                           
    #19 0x564d06253f1a in __rust_try test.88cba25a81f58bb2-cgu.01                                                                                                                                                                             
    #20 0x564d0626f0cd in std::panic::catch_unwind::h8f677ee8bbe12892 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:142:14                                                   
    #21 0x564d062bdd34 in test::run_test::_$u7b$$u7b$closure$u7d$$u7d$::h770d75ef81e5dcab /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:566:43                                
    #22 0x564d062be4ef in test::run_test::_$u7b$$u7b$closure$u7d$$u7d$::h741ff39f58d00d99 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:594:41                                
    #23 0x564d0629233d in std::sys_common::backtrace::__rust_begin_short_backtrace::h999d2246d47b9dad /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:155:18    
    #24 0x564d0626f0ad in std::panic::catch_unwind::h5cf57153779a382a /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:142:14                                                   
    #25 0x564d0626fcbe in core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::h4988421d436d964a /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:
5                                                                                                                                                                                                                                             
    #26 0x564d06542c16 in _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::h1a4bedae61e84932 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/libra
ry/alloc/src/boxed.rs:2016:9
    #27 0x564d0656ffb3 in std::sys::pal::unix::thread::Thread::new::thread_start::h9a0c56955487e0f2 /home/vext01/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/pal/unix/thread.rs:108:17
    #28 0x564d04b19ea8 in asan_thread_start(void*) /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
...

They are likely to be real leaks, as the unstable book says:

It [asan] is not expected to produce false positive reports.

@wtfsck
Copy link
Member

wtfsck commented Feb 21, 2024

Is it because of the lazy_statics? Lazy static memory is never freed.

@vext01
Copy link
Author

vext01 commented Feb 21, 2024

In this instance, it does seem related to a lazy_static.

A lazy_static won't trigger asan alone. For example asan is fine with:

use lazy_static::lazy_static;

lazy_static! {
    static ref X: usize = 100;
}

fn main() {
    println!("{}", *X);
}

In iced, one of the problematic lazy statics is the result of a function that leaks a Box<T>. This could be (one of the) issue(s)?

I can make a similar leak with this minimal example:

use lazy_static::lazy_static;

fn f() -> usize {
    Box::leak(Box::new(1u8));
    0
}

lazy_static! {
    static ref X: usize = f();
}

fn main() {
    println!("{}", *X);
}

Maybe it can be fixed by keeping a reference to the Box alive?

@wtfsck
Copy link
Member

wtfsck commented Feb 21, 2024

Maybe it can be fixed by keeping a reference to the Box alive?

Looks like it's saved in the mnemonics vec which is then saved in the lazy static.

@vext01
Copy link
Author

vext01 commented Feb 21, 2024

Right, but I suspect it's because you store a raw pointer.

Having read the code a bit more, the pattern you use is like this:

use lazy_static::lazy_static;

#[derive(Debug)]
struct Thingy(*const u8);

unsafe impl Send for Thingy {}
unsafe impl Sync for Thingy{}

fn f() -> Vec<Thingy> {
    let mut outer_vec = Vec::new();
    let inner_vec = Vec::new();
    let leaked = Box::leak(Box::new(inner_vec)).as_ptr();
    outer_vec.push(Thingy(leaked));
    outer_vec
}

lazy_static! {
    static ref X: Vec<Thingy> = f();
}

fn main() {
    println!("{:?}", *X);
}

^ This does upset asan.

Whereas this doesn't:

use lazy_static::lazy_static;

#[derive(Debug)]
struct Thingy(&'static mut Vec<u8>);

fn f() -> Vec<Thingy> {
    let mut outer_vec = Vec::new();
    let inner_vec = Vec::new();
    let leaked = Box::leak(Box::new(inner_vec));
    outer_vec.push(Thingy(leaked));
    outer_vec
}

lazy_static! {
    static ref X: Vec<Thingy> = f();
}

fn main() {
    println!("{:?}", *X);
}

It seems that keeping a static reference to a leaked box to something is fine for asan, but keeping a raw pointer to a leaked box alone is not sufficient.

Note also the deletion of the unsafe impl in the latter.

@wtfsck
Copy link
Member

wtfsck commented Feb 21, 2024

So it's not a memory leak just a false positive. If you want to implement the workaround, I'll accept the PR.

@vext01
Copy link
Author

vext01 commented Feb 21, 2024

just a false positive

I'm not sure. Could be the case that with a reference, the memory is actually freed, whereas with a raw pointer it is not... I don't know.

I'm out of my depth here, so I think I need to ask on Zulip.

@wtfsck
Copy link
Member

wtfsck commented Feb 21, 2024

It's a lazy static so nothing will be freed.

@vext01
Copy link
Author

vext01 commented Feb 21, 2024

Yeah, that's my intuition too.

The lazy_static docs even say:

If the type has a destructor, then it will not run when the process exits.

So why does asan not puke on the example where we keep hold of a reference to the leaked box?

@wtfsck
Copy link
Member

wtfsck commented Feb 21, 2024

I don't know anything about the implementation of ASAN, but your two examples above show it's a false positive.

@vext01
Copy link
Author

vext01 commented Feb 21, 2024

Having spoken on Zulip, it seems likely that the second example is a false negative.

I'll see if I can fix this, but if it gets too difficult, I reserve the right to wimp out :)

@wtfsck
Copy link
Member

wtfsck commented Feb 21, 2024

Do you mean false positive? I don't see how your examples above have memory leaks since everything is stored in a lazy static which never gets freed and is never supposed to be freed (it's 'static lifetime).

@vext01
Copy link
Author

vext01 commented Feb 22, 2024

@vext01
Copy link
Author

vext01 commented Feb 22, 2024

I've had a look at a fix, and I don't think it's something I'm going to get right.

I think you need to hold a rust reference to all static memory at the time the process exits. I was trying to hold a slice to the vector backing, but it quickly got complicated.

@Dr-Emann
Copy link
Contributor

Dr-Emann commented Feb 23, 2024

The problem isn't related to pointer vs reference (asan doesn't know anything about pointers vs references)

Manually expanding out the example that ASAN complains about:

let mut outer_vec: Vec<Thingy> = Vec::new();
let inner_vec: Vec<u8> = Vec::new(); // inner_vec is on the stack, its data is allocated on the heap
let tmp_boxed_vec: Box<Vec<u8>> = Box::new(inner_vec); // Now we box the vec itself (a data pointer, a length and a capacity), so there are two allocations, the box, and then the data inside the vec.
let tmp_vec_ref: &'static Vec<u8> = Box::leak(tmp_boxed_vec);
let leaked: *const u8 = tmp_vec_ref.as_ptr(); // We no longer have a pointer to the Vec which was boxed, only the vec's data
outer_vec.push(Thingy(leaked));
outer_vec

Instead, you can use Vec::leak directly, or Box::leak(vec.into_boxed_slice()) (to resize the allocation to exactly the right size rather than possibly keeping extra capacity).

@Dr-Emann
Copy link
Contributor

Planning to open a PR to fix

@vext01
Copy link
Author

vext01 commented Feb 24, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants