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
empty libtest harness under cargo-careful fails valgrind #4
Comments
Which binary are you running here? I am confused what this is a bug in. But it seems to me this is running the empty libtest harness and pointing at something in there? So it would be a bug in libtest, getopts, or valgrind, but not cargo-careful? That libtest harness is extensively tested by Miri though. |
yeah, it fails on libtest even before getting to the tests.
I agree, but it only happens under the cargo-careful configuration, and not with a regular |
Note that getopts+unicode-width(the only dep getopts has) both have 0 unsafe in them. |
If you use regular cargo and set
does that trigger the problem? |
I assume you meant |
I don't know what exactly build-std does, it might not apply the flags to the standard library. So looks like this has to do with compiling the standard library with debug assertions? |
As far as I can tell, yes |
Assuming the line numbers are correct, it points at this debug assertion None of this can really be uninit though, given that Miri has been all over that code, so my assumption is that LLVM did something odd to the assembly that confuses valgrind, or some such thing. That's way outside of what I can debug, sorry. |
So we have 000000000006a380 <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E>:
6a380: 41 57 push %r15
6a382: 41 56 push %r14
6a384: 41 54 push %r12
6a386: 53 push %rbx
6a387: 48 83 ec 68 sub $0x68,%rsp
6a38b: 48 89 54 24 18 mov %rdx,0x18(%rsp)
6a390: 48 89 4c 24 20 mov %rcx,0x20(%rsp)
6a395: 4c 8b 3e mov (%rsi),%r15
6a398: 41 f6 c7 07 test $0x7,%r15b
6a39c: 0f 85 e6 00 00 00 jne 6a488 <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E+0x108>
6a3a2: 49 89 d0 mov %rdx,%r8
6a3a5: 48 89 f3 mov %rsi,%rbx
6a3a8: 4c 8b 66 10 mov 0x10(%rsi),%r12
6a3ac: ba 38 00 00 00 mov $0x38,%edx
6a3b1: 4c 89 e0 mov %r12,%rax
6a3b4: 48 f7 e2 mul %rdx
6a3b7: 0f 80 cb 00 00 00 jo 6a488 <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E+0x108>
6a3bd: 48 85 c0 test %rax,%rax
6a3c0: 0f 88 c2 00 00 00 js 6a488 <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E+0x108>
6a3c6: 49 89 fe mov %rdi,%r14
6a3c9: 48 83 f9 01 cmp $0x1,%rcx
6a3cd: 75 12 jne 6a3e1 <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E+0x61>
6a3cf: 41 0f b6 00 movzbl (%r8),%eax
6a3d3: 89 44 24 08 mov %eax,0x8(%rsp)
6a3d7: 48 c7 04 24 00 00 00 movq $0x0,(%rsp)
6a3de: 00
6a3df: eb 0e jmp 6a3ef <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E+0x6f>
6a3e1: 48 89 e7 mov %rsp,%rdi
6a3e4: 4c 89 c6 mov %r8,%rsi
6a3e7: 48 89 ca mov %rcx,%rdx
6a3ea: e8 71 31 00 00 call 6d560 <_ZN5alloc5slice64_$LT$impl$u20$alloc..borrow..ToOwned$u20$for$u20$$u5b$T$u5d$$GT$8to_owned17he2b6ed39784f2498E>
6a3ef: 48 89 e2 mov %rsp,%rdx
6a3f2: 4c 89 ff mov %r15,%rdi
6a3f5: 4c 89 e6 mov %r12,%rsi
6a3f8: ff 15 b2 87 0b 00 call *0xb87b2(%rip) # 122bb0 <_GLOBAL_OFFSET_TABLE_+0x8f8>
... The call is to 000000000006a790 <_ZN7getopts8find_opt17ha49ce0943149df1eE>:
6a790: 55 push %rbp
6a791: 41 57 push %r15
6a793: 41 56 push %r14
6a795: 41 55 push %r13
6a797: 41 54 push %r12
6a799: 53 push %rbx
6a79a: 48 83 ec 28 sub $0x28,%rsp
6a79e: 48 85 f6 test %rsi,%rsi
6a7a1: 0f 84 a6 00 00 00 je 6a84d <_ZN7getopts8find_opt17ha49ce0943149df1eE+0xbd>
6a7a7: 49 89 fe mov %rdi,%r14
6a7aa: 4c 6b ce 38 imul $0x38,%rsi,%r9
6a7ae: 4c 8b 2a mov (%rdx),%r13
6a7b1: 4c 8b 42 10 mov 0x10(%rdx),%r8
6a7b5: 44 8b 52 08 mov 0x8(%rdx),%r10d
6a7b9: 4d 85 c0 test %r8,%r8
6a7bc: 4c 89 4c 24 10 mov %r9,0x10(%rsp)
6a7c1: 44 89 54 24 0c mov %r10d,0xc(%rsp)
6a7c6: 4c 89 44 24 20 mov %r8,0x20(%rsp)
6a7cb: 0f 88 83 00 00 00 js 6a854 <_ZN7getopts8find_opt17ha49ce0943149df1eE+0xc4> The last conditional jump is the one flagged by Valgrind. Relevant instructions are: 6a7b1: 4c 8b 42 10 mov 0x10(%rdx),%r8
...
6a7b9: 4d 85 c0 test %r8,%r8
...
6a7cb: 0f 88 83 00 00 00 js 6a854 <_ZN7getopts8find_opt17ha49ce0943149df1eE+0xc4> So Valgrind thinks that ...
6a387: 48 83 ec 68 sub $0x68,%rsp
6a38b: 48 89 54 24 18 mov %rdx,0x18(%rsp)
6a390: 48 89 4c 24 20 mov %rcx,0x20(%rsp)
...
6a3d3: 89 44 24 08 mov %eax,0x8(%rsp)
6a3d7: 48 c7 04 24 00 00 00 movq $0x0,(%rsp)
6a3de: 00
6a3df: eb 0e jmp 6a3ef <_ZN7getopts7Matches8opt_vals17hbf0997a509768c43E+0x6f>
...
6a3ef: 48 89 e2 mov %rsp,%rdx
...
6a3f8: ff 15 b2 87 0b 00 call *0xb87b2(%rip) # 122bb0 <_GLOBAL_OFFSET_TABLE_+0x8f8> And it looks like Valgrind is right. The value at enum Name {
/// A string representing the long name of an option.
/// For example: "help"
Long(String),
/// A char representing the short name of an option.
/// For example: 'h'
Short(char),
} and it's the It does seem weird that fn find_opt(opts: &[Opt], nm: &Name) -> Option<usize> {
// Search main options.
let pos = opts.iter().position(|opt| &opt.name == nm);
... |
Here's a reduction that doesn't depend on the test harness:
|
Does this reproduce without
|
Nope, looks like it needs the standard library to be built with those flags too. Ultimately I think this comes down to |
According to the valgrind location, it's this check in Probably copying enough things from the stdlib to the testcase can reproduce it without the custom stdlib -- not sure. |
Reproduced this without cargo-careful: main.rs#![feature(core_intrinsics, ptr_internals, strict_provenance, pointer_is_aligned)]
use core::intrinsics::discriminant_value;
use core::{alloc::Layout, ffi::c_int, hint::black_box, mem, ptr};
use std::{hint::unreachable_unchecked, ptr::NonNull};
pub struct Vec<T> {
ptr: NonNull<T>,
cap: usize,
len: usize,
}
impl<T> Vec<T> {
pub fn with_cap(cap: usize) -> Self {
let layout = Layout::array::<T>(cap).unwrap();
if usize::BITS < 64 && layout.size() > isize::MAX as usize {
unimplemented!();
}
let ptr = unsafe { std::alloc::alloc(layout) };
Self {
ptr: NonNull::new(ptr).unwrap().cast(),
cap,
len: 0,
}
}
pub fn from_slice(s: &[T]) -> Self {
let mut vec = Self::with_cap(s.len());
unsafe { ptr::copy_nonoverlapping(s.as_ptr(), vec.ptr.as_mut(), s.len()) };
vec.len = s.len();
vec
}
pub fn as_slice(&self) -> &[T] {
let data = self.as_ptr();
let len = self.len;
if is_aligned_and_not_null(data)
&& crate::mem::size_of::<T>().saturating_mul(len) <= isize::MAX as usize
{
panic!();
}
unsafe { &*ptr::slice_from_raw_parts(data, len) }
}
pub fn as_ptr(&self) -> *const T {
self.ptr.as_ptr()
}
}
impl<T> PartialEq for Vec<T> {
fn eq(&self, other: &Self) -> bool {
let s = self.as_slice();
let o = other.as_slice();
if s.len() != o.len() {
return false;
}
extern "C" {
fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> c_int;
}
unsafe {
let size = mem::size_of_val(self);
memcmp(s.as_ptr() as *const u8, o.as_ptr() as *const u8, size) == 0
}
}
}
struct String {
vec: Vec<u8>,
}
impl String {
fn from_str(s: &str) -> String {
let bytes = s.as_bytes();
String {
vec: Vec::from_slice(bytes),
}
}
}
impl PartialEq for String {
fn eq(&self, other: &String) -> bool {
self.vec == other.vec
}
}
enum Name {
Long(String),
Short(char),
}
impl PartialEq for Name {
fn eq(&self, other: &Name) -> bool {
let self_tag = discriminant_value(self);
let other_tag = discriminant_value(other);
self_tag == other_tag
&& match (self, other) {
(Name::Long(self_0), Name::Long(other_0)) => self_0 == other_0,
(Name::Short(self_0), Name::Short(other_0)) => self_0 == other_0,
_ => unsafe { unreachable_unchecked() },
}
}
}
impl Name {
fn from_str(nm: &str) -> Name {
if nm.len() == 1 {
Name::Short(nm.as_bytes()[0] as char)
} else {
Name::Long(String::from_str(nm))
}
}
}
struct Opt {
name: Name,
}
#[inline(never)]
fn find_opt(opts: &[Opt], nm: &Name) -> Option<usize> {
// Search main options.
let mut pos = None;
for (i, opt) in opts.iter().enumerate() {
if &opt.name == nm {
pos = Some(i);
break;
}
}
pos
}
fn main() {
let opts = &[Opt {
name: Name::from_str("h"),
}][..];
let nm = "h";
let name = Name::from_str(nm);
black_box(find_opt(opts, &name));
}
pub(crate) fn is_aligned_and_not_null<T>(ptr: *const T) -> bool {
!ptr.is_null() && ptr.is_aligned()
} (I'll work on minimizing this more) This fails with:
valgrind output:
|
I think the exact lines of valgrind are wrong, this might be a miscompilation thing |
Miri is fine with that code... so yeah either a miscompilation or a valgrind false positive. Sounds like this should be turned into a rustc bug report then? |
Yes, I edited rust-lang/rust#102712 to reflect the conversation here, so I guess this can be closed |
Steps to reproduce:
Valgrind output:
I've tried to debug this with gdb and adding prints, but it disappears if you look at it too closely so gave up after a bit of trying
The text was updated successfully, but these errors were encountered: