我的 Rust 单元测试出现 'Invalid utf-8 sequence of 1 bytes from index 1' 和 'munmup_chunk(): invalid pointer' 错误
'Invalid utf-8 sequence of 1 bytes from index 1' and 'munmup_chunk(): invalid pointer' error in my Rust unit-tests
只有 lib.rs 个文件:
use libc::{self, O_RDONLY, strerror, open, close, __errno_location, read};
use std::ffi::{CString, CStr};
use std::{fmt, process};
// File descriptor
struct Fd {
fd: i32,
}
impl Fd {
fn new(fd: i32) -> Fd {
Fd { fd }
}
fn get(&self) -> i32 {
self.fd
}
}
impl Drop for Fd {
fn drop(&mut self) {
unsafe {
println!("closing fd - {}", self.fd);
match close(self.fd) {
-1 => Err( LacError::new().unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);
}) ).unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);}),
_ => (),
}
}
}
}
// Linux API call Error
#[derive(Debug)]
struct LacError(String);
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok( LacError( CString::from_raw(strerror(*__errno_location()))
.into_string()?) )
}
}
}
impl fmt::Display for LacError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for LacError {}
// lac (Linux API call) functions
fn lac_open(path: &str) -> Result<Fd, Box<dyn std::error::Error>> {
unsafe {
let path_holder = CString::new(path)?;
let path_ptr = path_holder.as_ptr();
match open(path_ptr, O_RDONLY) {
-1 => Err(Box::new(LacError::new()?)),
fd => Ok(Fd::new(fd))
}
}
}
fn lac_read(fd: &Fd,
buf: &mut String,
count: usize) -> Result<isize, Box<dyn std::error::Error>> {
let buf_holder = CString::new("")?;
let buf_ptr = buf_holder.as_ptr();
unsafe {
match read(fd.get(), buf_ptr as *mut libc::c_void, count) {
0 => Ok(0),
-1 => Err(Box::new(LacError::new()?)),
num_of_red_bytes => {
buf.push_str(CStr::from_ptr(buf_ptr).to_str()?);
Ok(num_of_red_bytes)
},
}
}
}
// TESTS
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn whether_correctly_open() {
let path = "test_file.txt";
assert_ne!(match lac_open(path) {Ok(fd) => fd.get(), Err(_) => -1},
-1);
}
#[test]
#[should_panic]
fn whether_correctly_panic() {
let path = "testfile.txt";// For first issue "testfile.txt", for second "test_file.txt"
match lac_open(path) {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
}
#[test]
fn whether_correctly_read() {
let path = "test_file.txt";
let mut buf = String::from("");
let fd = lac_open(path)
.unwrap_or_else(|e| {panic!("{}", e);});
let count = lac_read(&fd, &mut buf, 1)
.unwrap_or_else(|e| {panic!("{}", e);});
println!("{} {}", buf, count);
assert_eq!(buf, "s");
}
}
起初当我 运行 'cargo test -- -show-output' 第一个测试成功通过但第二个(第三个测试省略了一段时间)不仅失败了(
running 3 tests
test tests::whether_correctly_open ... ok
munmap_chunk(): invalid pointer
error: test failed, to rerun pass '--lib'
Caused by:
process didn't exit successfully: `/home/Michail/rPrgs/usl/target/debug/deps/usl-1be62c27ff5543fb --show-output` (signal: 6, SIGABRT: process abort signal)
) OS 向该进程发送信号可能是因为 LacError::new()
-method:
中的操作
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok( LacError( CString::from_raw(strerror(*__errno_location()))
.into_string()?) )
}
}
}
而且我完全不知道我在哪里犯了错误。
第二次,当我将“testfile.txt”与“test_file.txt”交换并使第二次测试真的失败时,我 运行 'cargo test -- --show-output' 和(我相信`原因'cargo test' 运行 默认情况下在几个线程中)接收
running 3 tests
test tests::whether_correctly_open ... ok
test tests::whether_correctly_panic ... FAILED
test tests::whether_correctly_read ... ok
successes:
---- tests::whether_correctly_open stdout ----
closing fd - 3
---- tests::whether_correctly_read stdout ----
s 1
closing fd - 3
successes:
tests::whether_correctly_open
tests::whether_correctly_read
failures:
---- tests::whether_correctly_panic stdout ----
closing fd - 3
note: test did not panic as expected
failures:
tests::whether_correctly_panic
test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
如果几次运行
test tests::whether_correctly_open ... ok
test tests::whether_correctly_panic ... FAILED
test tests::whether_correctly_read ... FAILED
successes:
---- tests::whether_correctly_open stdout ----
closing fd - 3
successes:
tests::whether_correctly_open
failures:
---- tests::whether_correctly_panic stdout ----
closing fd - 3
note: test did not panic as expected
---- tests::whether_correctly_read stdout ----
thread 'tests::whether_correctly_read' panicked at 'invalid utf-8 sequence of 1 bytes from index 2', src/lib.rs:119:34
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
closing fd - 3
failures:
tests::whether_correctly_panic
tests::whether_correctly_read
test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
也就是说,随着时间的推移,当我从上面重复 运行 命令时,我会收到不同的结果。我认为与该代码有关的 utf-8 问题
fn lac_read(fd: &Fd,
buf: &mut String,
count: usize) -> Result<isize, Box<dyn std::error::Error>> {
let buf_holder = CString::new("")?;
let buf_ptr = buf_holder.as_ptr();
unsafe {
match read(fd.get(), buf_ptr as *mut libc::c_void, count) {
0 => Ok(0),
-1 => Err(Box::new(LacError::new()?)),
num_of_red_bytes => {
buf.push_str(CStr::from_ptr(buf_ptr).to_str()?);//<-----------------------
Ok(num_of_red_bytes)
},
}
}
}
此外,当我重复运行 'cargo test -- --show-output --test-threads=1' 第三次测试总是失败。
UPD:在测试之前,我通过
在“test_file.txt”中编写了常见的拉丁字符
echo sometext > test_file.txt
最终更新:第一期我决定只是从借用的 strerror 字符串创建新的拥有的字符串:
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok( LacError( String::from( CStr::from_ptr(strerror(*__errno_location())).to_str()? ) ) )
}
}
}
第二个最有趣,因为 CString 不是可增长的字符串,读取系统调用将字符插入空格。我决定编写 for
循环以在临时 'container' 中推送 'count' 个空间,这是可增长的拥有字符串:
fn lac_read(fd: &Fd,
buf: &mut String,
count: usize) -> Result<isize, Box<dyn std::error::Error>> {
let mut container = String::new();
for _ in 0..count {
container.push(' ');
}
let buf_holder = CString::new(container.as_str())?;
let buf_ptr = buf_holder.into_raw();
unsafe {
match read(fd.get(), buf_ptr as *mut libc::c_void, count) {
0 => Ok(0),
-1 => Err(Box::new(LacError::new()?)),
num_of_red_bytes => {
buf.push_str(CString::from_raw(buf_ptr).to_str()?);
Ok(num_of_red_bytes)
},
}
}
}
问题是 CString
会在超出范围时尝试取消分配其字符串。相反,你应该使用 CStr
不会这样做:
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok(LacError(
CStr::from_ptr(strerror(*__errno_location())).to_string_lossy().to_string()
))
}
}
}
通常 creates/allocates/etc 负责 destruction/de-allocation/etc,除非文档另有明确说明。由于字符串是由 OS、.
分配的
下一个问题:为什么你的第三次测试失败了:
read()
调用将最多 count
个字节读入提供的缓冲区。但它不会 调整缓冲区大小。您的应用程序创建了一个大小为 1 的缓冲区(它是 1,因为 C 字符串以 0 终止):
let buf_holder = CString::new("")?;
问题是 C 字符串 必须 0 终止。因此,如果您读取的内容不是 0,CStr::from_ptr()
将尝试从缓冲区外的未初始化内存中读取,这是未定义的行为。您可以通过更改计数使其 100% 可重现:
let count = lac_read(&fd, &mut buf, 100).unwrap();
现在您的应用程序将尝试将 100 个字节读入该 1 字节缓冲区,破坏它之后的所有内容。有了它,我总是得到:
malloc(): corrupted top size
因此,为了解决这个问题,您必须确保您的缓冲区足够大以容纳数据(并且不要忘记尾随 0!)
只有 lib.rs 个文件:
use libc::{self, O_RDONLY, strerror, open, close, __errno_location, read};
use std::ffi::{CString, CStr};
use std::{fmt, process};
// File descriptor
struct Fd {
fd: i32,
}
impl Fd {
fn new(fd: i32) -> Fd {
Fd { fd }
}
fn get(&self) -> i32 {
self.fd
}
}
impl Drop for Fd {
fn drop(&mut self) {
unsafe {
println!("closing fd - {}", self.fd);
match close(self.fd) {
-1 => Err( LacError::new().unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);
}) ).unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);}),
_ => (),
}
}
}
}
// Linux API call Error
#[derive(Debug)]
struct LacError(String);
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok( LacError( CString::from_raw(strerror(*__errno_location()))
.into_string()?) )
}
}
}
impl fmt::Display for LacError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for LacError {}
// lac (Linux API call) functions
fn lac_open(path: &str) -> Result<Fd, Box<dyn std::error::Error>> {
unsafe {
let path_holder = CString::new(path)?;
let path_ptr = path_holder.as_ptr();
match open(path_ptr, O_RDONLY) {
-1 => Err(Box::new(LacError::new()?)),
fd => Ok(Fd::new(fd))
}
}
}
fn lac_read(fd: &Fd,
buf: &mut String,
count: usize) -> Result<isize, Box<dyn std::error::Error>> {
let buf_holder = CString::new("")?;
let buf_ptr = buf_holder.as_ptr();
unsafe {
match read(fd.get(), buf_ptr as *mut libc::c_void, count) {
0 => Ok(0),
-1 => Err(Box::new(LacError::new()?)),
num_of_red_bytes => {
buf.push_str(CStr::from_ptr(buf_ptr).to_str()?);
Ok(num_of_red_bytes)
},
}
}
}
// TESTS
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn whether_correctly_open() {
let path = "test_file.txt";
assert_ne!(match lac_open(path) {Ok(fd) => fd.get(), Err(_) => -1},
-1);
}
#[test]
#[should_panic]
fn whether_correctly_panic() {
let path = "testfile.txt";// For first issue "testfile.txt", for second "test_file.txt"
match lac_open(path) {
Ok(_) => (),
Err(e) => panic!("{}", e),
}
}
#[test]
fn whether_correctly_read() {
let path = "test_file.txt";
let mut buf = String::from("");
let fd = lac_open(path)
.unwrap_or_else(|e| {panic!("{}", e);});
let count = lac_read(&fd, &mut buf, 1)
.unwrap_or_else(|e| {panic!("{}", e);});
println!("{} {}", buf, count);
assert_eq!(buf, "s");
}
}
起初当我 运行 'cargo test -- -show-output' 第一个测试成功通过但第二个(第三个测试省略了一段时间)不仅失败了(
running 3 tests
test tests::whether_correctly_open ... ok
munmap_chunk(): invalid pointer
error: test failed, to rerun pass '--lib'
Caused by:
process didn't exit successfully: `/home/Michail/rPrgs/usl/target/debug/deps/usl-1be62c27ff5543fb --show-output` (signal: 6, SIGABRT: process abort signal)
) OS 向该进程发送信号可能是因为 LacError::new()
-method:
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok( LacError( CString::from_raw(strerror(*__errno_location()))
.into_string()?) )
}
}
}
而且我完全不知道我在哪里犯了错误。
第二次,当我将“testfile.txt”与“test_file.txt”交换并使第二次测试真的失败时,我 运行 'cargo test -- --show-output' 和(我相信`原因'cargo test' 运行 默认情况下在几个线程中)接收
running 3 tests
test tests::whether_correctly_open ... ok
test tests::whether_correctly_panic ... FAILED
test tests::whether_correctly_read ... ok
successes:
---- tests::whether_correctly_open stdout ----
closing fd - 3
---- tests::whether_correctly_read stdout ----
s 1
closing fd - 3
successes:
tests::whether_correctly_open
tests::whether_correctly_read
failures:
---- tests::whether_correctly_panic stdout ----
closing fd - 3
note: test did not panic as expected
failures:
tests::whether_correctly_panic
test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
如果几次运行
test tests::whether_correctly_open ... ok
test tests::whether_correctly_panic ... FAILED
test tests::whether_correctly_read ... FAILED
successes:
---- tests::whether_correctly_open stdout ----
closing fd - 3
successes:
tests::whether_correctly_open
failures:
---- tests::whether_correctly_panic stdout ----
closing fd - 3
note: test did not panic as expected
---- tests::whether_correctly_read stdout ----
thread 'tests::whether_correctly_read' panicked at 'invalid utf-8 sequence of 1 bytes from index 2', src/lib.rs:119:34
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
closing fd - 3
failures:
tests::whether_correctly_panic
tests::whether_correctly_read
test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass '--lib'
也就是说,随着时间的推移,当我从上面重复 运行 命令时,我会收到不同的结果。我认为与该代码有关的 utf-8 问题
fn lac_read(fd: &Fd,
buf: &mut String,
count: usize) -> Result<isize, Box<dyn std::error::Error>> {
let buf_holder = CString::new("")?;
let buf_ptr = buf_holder.as_ptr();
unsafe {
match read(fd.get(), buf_ptr as *mut libc::c_void, count) {
0 => Ok(0),
-1 => Err(Box::new(LacError::new()?)),
num_of_red_bytes => {
buf.push_str(CStr::from_ptr(buf_ptr).to_str()?);//<-----------------------
Ok(num_of_red_bytes)
},
}
}
}
此外,当我重复运行 'cargo test -- --show-output --test-threads=1' 第三次测试总是失败。
UPD:在测试之前,我通过
在“test_file.txt”中编写了常见的拉丁字符echo sometext > test_file.txt
最终更新:第一期我决定只是从借用的 strerror 字符串创建新的拥有的字符串:
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok( LacError( String::from( CStr::from_ptr(strerror(*__errno_location())).to_str()? ) ) )
}
}
}
第二个最有趣,因为 CString 不是可增长的字符串,读取系统调用将字符插入空格。我决定编写 for
循环以在临时 'container' 中推送 'count' 个空间,这是可增长的拥有字符串:
fn lac_read(fd: &Fd,
buf: &mut String,
count: usize) -> Result<isize, Box<dyn std::error::Error>> {
let mut container = String::new();
for _ in 0..count {
container.push(' ');
}
let buf_holder = CString::new(container.as_str())?;
let buf_ptr = buf_holder.into_raw();
unsafe {
match read(fd.get(), buf_ptr as *mut libc::c_void, count) {
0 => Ok(0),
-1 => Err(Box::new(LacError::new()?)),
num_of_red_bytes => {
buf.push_str(CString::from_raw(buf_ptr).to_str()?);
Ok(num_of_red_bytes)
},
}
}
}
问题是 CString
会在超出范围时尝试取消分配其字符串。相反,你应该使用 CStr
不会这样做:
impl LacError {
fn new() -> Result<LacError, Box<dyn std::error::Error>> {
unsafe {
Ok(LacError(
CStr::from_ptr(strerror(*__errno_location())).to_string_lossy().to_string()
))
}
}
}
通常 creates/allocates/etc 负责 destruction/de-allocation/etc,除非文档另有明确说明。由于字符串是由 OS、
下一个问题:为什么你的第三次测试失败了:
read()
调用将最多 count
个字节读入提供的缓冲区。但它不会 调整缓冲区大小。您的应用程序创建了一个大小为 1 的缓冲区(它是 1,因为 C 字符串以 0 终止):
let buf_holder = CString::new("")?;
问题是 C 字符串 必须 0 终止。因此,如果您读取的内容不是 0,CStr::from_ptr()
将尝试从缓冲区外的未初始化内存中读取,这是未定义的行为。您可以通过更改计数使其 100% 可重现:
let count = lac_read(&fd, &mut buf, 100).unwrap();
现在您的应用程序将尝试将 100 个字节读入该 1 字节缓冲区,破坏它之后的所有内容。有了它,我总是得到:
malloc(): corrupted top size
因此,为了解决这个问题,您必须确保您的缓冲区足够大以容纳数据(并且不要忘记尾随 0!)