Skip to content

Commit 6ce5226

Browse files
committed
Auto merge of rust-lang#2620 - RalfJung:getname, r=RalfJung
add pthread_getname_np A new libstd test needs this, and there doesn't seem to be a good reason not to have this.
2 parents 136a1db + dac2412 commit 6ce5226

File tree

8 files changed

+152
-36
lines changed

8 files changed

+152
-36
lines changed

src/tools/miri/src/concurrency/thread.rs

+1
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
870870
this.machine.threads.active_thread_stack_mut()
871871
}
872872

873+
/// Set the name of the current thread. The buffer must not include the null terminator.
873874
#[inline]
874875
fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
875876
let this = self.eval_context_mut();

src/tools/miri/src/helpers.rs

+59
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod convert;
22

33
use std::cmp;
4+
use std::iter;
45
use std::mem;
56
use std::num::NonZeroUsize;
67
use std::time::Duration;
@@ -735,6 +736,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
735736
})
736737
}
737738

739+
/// Read a sequence of bytes until the first null terminator.
738740
fn read_c_str<'a>(&'a self, ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, &'a [u8]>
739741
where
740742
'tcx: 'a,
@@ -761,6 +763,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
761763
this.read_bytes_ptr_strip_provenance(ptr, len)
762764
}
763765

766+
/// Helper function to write a sequence of bytes with an added null-terminator, which is what
767+
/// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying
768+
/// to write if `size` is not large enough to fit the contents of `c_str` plus a null
769+
/// terminator. It returns `Ok((true, length))` if the writing process was successful. The
770+
/// string length returned does include the null terminator.
771+
fn write_c_str(
772+
&mut self,
773+
c_str: &[u8],
774+
ptr: Pointer<Option<Provenance>>,
775+
size: u64,
776+
) -> InterpResult<'tcx, (bool, u64)> {
777+
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null
778+
// terminator to memory using the `ptr` pointer would cause an out-of-bounds access.
779+
let string_length = u64::try_from(c_str.len()).unwrap();
780+
let string_length = string_length.checked_add(1).unwrap();
781+
if size < string_length {
782+
return Ok((false, string_length));
783+
}
784+
self.eval_context_mut()
785+
.write_bytes_ptr(ptr, c_str.iter().copied().chain(iter::once(0u8)))?;
786+
Ok((true, string_length))
787+
}
788+
789+
/// Read a sequence of u16 until the first null terminator.
764790
fn read_wide_str(&self, mut ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
765791
let this = self.eval_context_ref();
766792
let size2 = Size::from_bytes(2);
@@ -783,6 +809,39 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
783809
Ok(wchars)
784810
}
785811

812+
/// Helper function to write a sequence of u16 with an added 0x0000-terminator, which is what
813+
/// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
814+
/// to write if `size` is not large enough to fit the contents of `os_string` plus a null
815+
/// terminator. It returns `Ok((true, length))` if the writing process was successful. The
816+
/// string length returned does include the null terminator. Length is measured in units of
817+
/// `u16.`
818+
fn write_wide_str(
819+
&mut self,
820+
wide_str: &[u16],
821+
ptr: Pointer<Option<Provenance>>,
822+
size: u64,
823+
) -> InterpResult<'tcx, (bool, u64)> {
824+
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required
825+
// 0x0000 terminator to memory would cause an out-of-bounds access.
826+
let string_length = u64::try_from(wide_str.len()).unwrap();
827+
let string_length = string_length.checked_add(1).unwrap();
828+
if size < string_length {
829+
return Ok((false, string_length));
830+
}
831+
832+
// Store the UTF-16 string.
833+
let size2 = Size::from_bytes(2);
834+
let this = self.eval_context_mut();
835+
let mut alloc = this
836+
.get_ptr_alloc_mut(ptr, size2 * string_length, Align::from_bytes(2).unwrap())?
837+
.unwrap(); // not a ZST, so we will get a result
838+
for (offset, wchar) in wide_str.iter().copied().chain(iter::once(0x0000)).enumerate() {
839+
let offset = u64::try_from(offset).unwrap();
840+
alloc.write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar))?;
841+
}
842+
Ok((true, string_length))
843+
}
844+
786845
/// Check that the ABI is what we expect.
787846
fn check_abi<'a>(&self, abi: Abi, exp_abi: Abi) -> InterpResult<'a, ()> {
788847
if self.eval_context_ref().machine.enforce_abi && abi != exp_abi {

src/tools/miri/src/shims/os_str.rs

+2-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::borrow::Cow;
22
use std::ffi::{OsStr, OsString};
3-
use std::iter;
43
use std::path::{Path, PathBuf};
54

65
#[cfg(unix)]
@@ -9,7 +8,6 @@ use std::os::unix::ffi::{OsStrExt, OsStringExt};
98
use std::os::windows::ffi::{OsStrExt, OsStringExt};
109

1110
use rustc_middle::ty::layout::LayoutOf;
12-
use rustc_target::abi::{Align, Size};
1311

1412
use crate::*;
1513

@@ -100,16 +98,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10098
size: u64,
10199
) -> InterpResult<'tcx, (bool, u64)> {
102100
let bytes = os_str_to_bytes(os_str)?;
103-
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null
104-
// terminator to memory using the `ptr` pointer would cause an out-of-bounds access.
105-
let string_length = u64::try_from(bytes.len()).unwrap();
106-
let string_length = string_length.checked_add(1).unwrap();
107-
if size < string_length {
108-
return Ok((false, string_length));
109-
}
110-
self.eval_context_mut()
111-
.write_bytes_ptr(ptr, bytes.iter().copied().chain(iter::once(0u8)))?;
112-
Ok((true, string_length))
101+
self.eval_context_mut().write_c_str(bytes, ptr, size)
113102
}
114103

115104
/// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what
@@ -140,25 +129,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
140129
}
141130

142131
let u16_vec = os_str_to_u16vec(os_str)?;
143-
// If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required
144-
// 0x0000 terminator to memory would cause an out-of-bounds access.
145-
let string_length = u64::try_from(u16_vec.len()).unwrap();
146-
let string_length = string_length.checked_add(1).unwrap();
147-
if size < string_length {
148-
return Ok((false, string_length));
149-
}
150-
151-
// Store the UTF-16 string.
152-
let size2 = Size::from_bytes(2);
153-
let this = self.eval_context_mut();
154-
let mut alloc = this
155-
.get_ptr_alloc_mut(ptr, size2 * string_length, Align::from_bytes(2).unwrap())?
156-
.unwrap(); // not a ZST, so we will get a result
157-
for (offset, wchar) in u16_vec.into_iter().chain(iter::once(0x0000)).enumerate() {
158-
let offset = u64::try_from(offset).unwrap();
159-
alloc.write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar))?;
160-
}
161-
Ok((true, string_length))
132+
self.eval_context_mut().write_wide_str(&u16_vec, ptr, size)
162133
}
163134

164135
/// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes.

src/tools/miri/src/shims/unix/freebsd/foreign_items.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
2626
"pthread_set_name_np" => {
2727
let [thread, name] =
2828
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
29-
let res =
30-
this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?;
29+
let max_len = usize::MAX; // freebsd does not seem to have a limit.
30+
let res = this.pthread_setname_np(
31+
this.read_scalar(thread)?,
32+
this.read_scalar(name)?,
33+
max_len,
34+
)?;
3135
this.write_scalar(res, dest)?;
3236
}
3337

src/tools/miri/src/shims/unix/linux/foreign_items.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
6868
"pthread_setname_np" => {
6969
let [thread, name] =
7070
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
71-
let res =
72-
this.pthread_setname_np(this.read_scalar(thread)?, this.read_scalar(name)?)?;
71+
let max_len = 16;
72+
let res = this.pthread_setname_np(
73+
this.read_scalar(thread)?,
74+
this.read_scalar(name)?,
75+
max_len,
76+
)?;
77+
this.write_scalar(res, dest)?;
78+
}
79+
"pthread_getname_np" => {
80+
let [thread, name, len] =
81+
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
82+
let res = this.pthread_getname_np(
83+
this.read_scalar(thread)?,
84+
this.read_scalar(name)?,
85+
this.read_scalar(len)?,
86+
)?;
7387
this.write_scalar(res, dest)?;
7488
}
7589

src/tools/miri/src/shims/unix/macos/foreign_items.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
176176
"pthread_setname_np" => {
177177
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
178178
let thread = this.pthread_self()?;
179-
this.pthread_setname_np(thread, this.read_scalar(name)?)?;
179+
let max_len = this.eval_libc("MAXTHREADNAMESIZE")?.to_machine_usize(this)?;
180+
this.pthread_setname_np(
181+
thread,
182+
this.read_scalar(name)?,
183+
max_len.try_into().unwrap(),
184+
)?;
185+
}
186+
"pthread_getname_np" => {
187+
let [thread, name, len] =
188+
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
189+
let res = this.pthread_getname_np(
190+
this.read_scalar(thread)?,
191+
this.read_scalar(name)?,
192+
this.read_scalar(len)?,
193+
)?;
194+
this.write_scalar(res, dest)?;
180195
}
181196

182197
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.

src/tools/miri/src/shims/unix/thread.rs

+27
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,49 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
6767
Ok(Scalar::from_machine_usize(thread_id.into(), this))
6868
}
6969

70+
/// Set the name of the current thread. `max_name_len` is the maximal length of the name
71+
/// including the null terminator.
7072
fn pthread_setname_np(
7173
&mut self,
7274
thread: Scalar<Provenance>,
7375
name: Scalar<Provenance>,
76+
max_name_len: usize,
7477
) -> InterpResult<'tcx, Scalar<Provenance>> {
7578
let this = self.eval_context_mut();
7679

7780
let thread = ThreadId::try_from(thread.to_machine_usize(this)?).unwrap();
7881
let name = name.to_pointer(this)?;
7982

8083
let name = this.read_c_str(name)?.to_owned();
84+
85+
// Comparing with `>=` to account for null terminator.
86+
if name.len() >= max_name_len {
87+
return this.eval_libc("ERANGE");
88+
}
89+
8190
this.set_thread_name(thread, name);
8291

8392
Ok(Scalar::from_u32(0))
8493
}
8594

95+
fn pthread_getname_np(
96+
&mut self,
97+
thread: Scalar<Provenance>,
98+
name_out: Scalar<Provenance>,
99+
len: Scalar<Provenance>,
100+
) -> InterpResult<'tcx, Scalar<Provenance>> {
101+
let this = self.eval_context_mut();
102+
103+
let thread = ThreadId::try_from(thread.to_machine_usize(this)?).unwrap();
104+
let name_out = name_out.to_pointer(this)?;
105+
let len = len.to_machine_usize(this)?;
106+
107+
let name = this.get_thread_name(thread).to_owned();
108+
let (success, _written) = this.write_c_str(&name, name_out, len)?;
109+
110+
if success { Ok(Scalar::from_u32(0)) } else { this.eval_libc("ERANGE") }
111+
}
112+
86113
fn sched_yield(&mut self) -> InterpResult<'tcx, i32> {
87114
let this = self.eval_context_mut();
88115

src/tools/miri/tests/pass-dep/shims/pthreads.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
//@ignore-target-windows: No libc on Windows
2+
#![feature(cstr_from_bytes_until_nul)]
3+
use std::ffi::CStr;
4+
use std::thread;
25

36
fn main() {
47
test_mutex_libc_init_recursive();
58
test_mutex_libc_init_normal();
69
test_mutex_libc_init_errorcheck();
710
test_rwlock_libc_static_initializer();
11+
test_named_thread_truncation();
812

913
#[cfg(any(target_os = "linux"))]
1014
test_mutex_libc_static_initializer_recursive();
@@ -125,3 +129,24 @@ fn test_rwlock_libc_static_initializer() {
125129
assert_eq!(libc::pthread_rwlock_destroy(rw.get()), 0);
126130
}
127131
}
132+
133+
fn test_named_thread_truncation() {
134+
let long_name = std::iter::once("test_named_thread_truncation")
135+
.chain(std::iter::repeat(" yada").take(100))
136+
.collect::<String>();
137+
138+
let result = thread::Builder::new().name(long_name.clone()).spawn(move || {
139+
// Rust remembers the full thread name itself.
140+
assert_eq!(thread::current().name(), Some(long_name.as_str()));
141+
142+
// But the system is limited -- make sure we successfully set a truncation.
143+
let mut buf = vec![0u8; long_name.len() + 1];
144+
unsafe {
145+
libc::pthread_getname_np(libc::pthread_self(), buf.as_mut_ptr().cast(), buf.len());
146+
}
147+
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
148+
assert!(cstr.to_bytes().len() >= 15); // POSIX seems to promise at least 15 chars
149+
assert!(long_name.as_bytes().starts_with(cstr.to_bytes()));
150+
});
151+
result.unwrap().join().unwrap();
152+
}

0 commit comments

Comments
 (0)