Skip to content

Commit a28f230

Browse files
committed
[nextest-runner] remove some unsafe code
Use explicit indexes so the unsafety is not necessary. We do still use it for `offset_from`, though.
1 parent 55d214f commit a28f230

File tree

2 files changed

+125
-76
lines changed

2 files changed

+125
-76
lines changed

nextest-runner/src/reporter/displayer.rs

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//!
66
//! The main structure in this module is [`TestReporter`].
77
8-
use super::structured::StructuredReporter;
8+
use super::{helpers::ByteSubslice, structured::StructuredReporter};
99
use crate::{
1010
config::{NextestProfile, ScriptId},
1111
errors::WriteEventError,
@@ -1376,14 +1376,11 @@ impl<'a> TestReporterImpl<'a> {
13761376
self.write_instance(*test_instance, writer)?;
13771377
writeln!(writer, "{}", " ---".style(header_style))?;
13781378

1379-
// SAFETY: stdout matches d.stdout_output
1380-
unsafe {
1381-
self.write_test_single_output_with_description(
1382-
stdout,
1383-
description.and_then(|d| d.stdout_output()),
1384-
writer,
1385-
)?;
1386-
}
1379+
self.write_test_single_output_with_description(
1380+
stdout,
1381+
description.and_then(|d| d.stdout_subslice()),
1382+
writer,
1383+
)?;
13871384
}
13881385

13891386
if !stderr.is_empty() {
@@ -1399,14 +1396,11 @@ impl<'a> TestReporterImpl<'a> {
13991396
self.write_instance(*test_instance, writer)?;
14001397
writeln!(writer, "{}", " ---".style(header_style))?;
14011398

1402-
// SAFETY: stderr matches d.stderr_output
1403-
unsafe {
1404-
self.write_test_single_output_with_description(
1405-
stderr,
1406-
description.and_then(|d| d.stderr_output()),
1407-
writer,
1408-
)?;
1409-
}
1399+
self.write_test_single_output_with_description(
1400+
stderr,
1401+
description.and_then(|d| d.stderr_subslice()),
1402+
writer,
1403+
)?;
14101404
}
14111405
}
14121406
TestOutput::Combined { output } => {
@@ -1423,14 +1417,11 @@ impl<'a> TestReporterImpl<'a> {
14231417
self.write_instance(*test_instance, writer)?;
14241418
writeln!(writer, "{}", " ---".style(header_style))?;
14251419

1426-
// SAFETY: output matches d.combined_output
1427-
unsafe {
1428-
self.write_test_single_output_with_description(
1429-
output,
1430-
description.and_then(|d| d.combined_output()),
1431-
writer,
1432-
)?;
1433-
}
1420+
self.write_test_single_output_with_description(
1421+
output,
1422+
description.and_then(|d| d.combined_subslice()),
1423+
writer,
1424+
)?;
14341425
}
14351426
}
14361427
}
@@ -1467,27 +1458,23 @@ impl<'a> TestReporterImpl<'a> {
14671458
writer: &mut dyn Write,
14681459
) -> io::Result<()> {
14691460
// SAFETY: The description is not provided.
1470-
unsafe { self.write_test_single_output_with_description(output, None, writer) }
1461+
self.write_test_single_output_with_description(output, None, writer)
14711462
}
14721463

1473-
/// Writes a test output to the writer, along with a subslice of the output to highlight.
1474-
///
1475-
/// # Safety
1464+
/// Writes a test output to the writer, along with optionally a subslice of the output to
1465+
/// highlight.
14761466
///
1477-
/// `description`, if provided, must be a subslice of `output.buf`.
1478-
unsafe fn write_test_single_output_with_description(
1467+
/// The description must be a subslice of the output.
1468+
fn write_test_single_output_with_description(
14791469
&self,
14801470
output: &TestSingleOutput,
1481-
description: Option<&[u8]>,
1471+
description: Option<ByteSubslice<'_>>,
14821472
writer: &mut dyn Write,
14831473
) -> io::Result<()> {
14841474
if self.styles.is_colorized {
14851475
const RESET_COLOR: &[u8] = b"\x1b[0m";
1486-
if let Some(subslice) = description {
1487-
// SAFETY: Preconditions are guaranteed by the caller.
1488-
let start =
1489-
unsafe { subslice.as_ptr().byte_offset_from(output.buf.as_ptr()) } as usize;
1490-
let end = start + highlight_end(subslice);
1476+
if let Some(ByteSubslice { start, slice }) = description {
1477+
let end = start + highlight_end(slice);
14911478

14921479
// Output the start and end of the test without stripping ANSI escapes, then reset
14931480
// the color afterwards in case the output is malformed.

nextest-runner/src/reporter/helpers.rs

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -39,55 +39,63 @@ pub enum DescriptionKind<'a> {
3939
///
4040
/// The output is borrowed from standard error.
4141
StackTrace {
42-
/// The stack trace as a subslice of the standard error.
43-
stderr_output: &'a [u8],
42+
/// The subslice of standard error that contains the stack trace.
43+
stderr_subslice: ByteSubslice<'a>,
4444
},
4545

4646
/// An error string was found in the output.
4747
///
4848
/// The output is borrowed from standard error.
4949
ErrorStr {
50-
/// The error string as a subslice of the standard error.
51-
stderr_output: &'a [u8],
50+
/// The subslice of standard error that contains the stack trace.
51+
stderr_subslice: ByteSubslice<'a>,
5252
},
5353

5454
/// A should-panic test did not panic.
5555
///
5656
/// The output is borrowed from standard output.
5757
ShouldPanic {
58-
/// The should-panic of the test as a subslice of the standard output.
59-
stdout_output: &'a [u8],
58+
/// The subslice of standard output that contains the should-panic message.
59+
stdout_subslice: ByteSubslice<'a>,
6060
},
6161
}
6262

6363
impl<'a> DescriptionKind<'a> {
6464
/// Returns the subslice of standard error that contains the description.
65-
pub fn stderr_output(&self) -> Option<&'a [u8]> {
65+
pub fn stderr_subslice(&self) -> Option<ByteSubslice<'a>> {
6666
match self {
6767
DescriptionKind::Abort { .. } => None,
68-
DescriptionKind::StackTrace { stderr_output } => Some(*stderr_output),
69-
DescriptionKind::ErrorStr { stderr_output } => Some(*stderr_output),
68+
DescriptionKind::StackTrace { stderr_subslice }
69+
| DescriptionKind::ErrorStr {
70+
stderr_subslice, ..
71+
} => Some(*stderr_subslice),
7072
DescriptionKind::ShouldPanic { .. } => None,
7173
}
7274
}
7375

7476
/// Returns the subslice of standard output that contains the description.
75-
pub fn stdout_output(&self) -> Option<&'a [u8]> {
77+
pub fn stdout_subslice(&self) -> Option<ByteSubslice<'a>> {
7678
match self {
7779
DescriptionKind::Abort { .. } => None,
7880
DescriptionKind::StackTrace { .. } => None,
7981
DescriptionKind::ErrorStr { .. } => None,
80-
DescriptionKind::ShouldPanic { stdout_output } => Some(*stdout_output),
82+
DescriptionKind::ShouldPanic {
83+
stdout_subslice, ..
84+
} => Some(*stdout_subslice),
8185
}
8286
}
8387

8488
/// Returns the subslice of combined output (either stdout or stderr) that contains the description.
85-
pub fn combined_output(&self) -> Option<&'a [u8]> {
89+
pub fn combined_subslice(&self) -> Option<ByteSubslice<'a>> {
8690
match self {
8791
DescriptionKind::Abort { .. } => None,
88-
DescriptionKind::StackTrace { stderr_output } => Some(*stderr_output),
89-
DescriptionKind::ErrorStr { stderr_output } => Some(*stderr_output),
90-
DescriptionKind::ShouldPanic { stdout_output } => Some(*stdout_output),
92+
DescriptionKind::StackTrace { stderr_subslice }
93+
| DescriptionKind::ErrorStr {
94+
stderr_subslice, ..
95+
} => Some(*stderr_subslice),
96+
DescriptionKind::ShouldPanic {
97+
stdout_subslice, ..
98+
} => Some(*stdout_subslice),
9199
}
92100
}
93101

@@ -97,6 +105,18 @@ impl<'a> DescriptionKind<'a> {
97105
}
98106
}
99107

108+
/// A subslice of a byte slice.
109+
///
110+
/// This type tracks the start index of the subslice from the parent slice.
111+
#[derive(Clone, Copy, Debug)]
112+
pub struct ByteSubslice<'a> {
113+
/// The slice.
114+
pub slice: &'a [u8],
115+
116+
/// The start index of the subslice from the parent slice.
117+
pub start: usize,
118+
}
119+
100120
/// A display wrapper for [`DescriptionKind`].
101121
#[derive(Clone, Copy, Debug)]
102122
pub struct DescriptionKindDisplay<'a>(DescriptionKind<'a>);
@@ -137,14 +157,15 @@ impl fmt::Display for DescriptionKindDisplay<'_> {
137157
}
138158
Ok(())
139159
}
140-
DescriptionKind::StackTrace { stderr_output } => {
141-
write!(f, "{}", String::from_utf8_lossy(stderr_output))
160+
DescriptionKind::StackTrace { stderr_subslice } => {
161+
// Strip invalid XML characters.
162+
write!(f, "{}", String::from_utf8_lossy(stderr_subslice.slice))
142163
}
143-
DescriptionKind::ErrorStr { stderr_output } => {
144-
write!(f, "{}", String::from_utf8_lossy(stderr_output))
164+
DescriptionKind::ErrorStr { stderr_subslice } => {
165+
write!(f, "{}", String::from_utf8_lossy(stderr_subslice.slice))
145166
}
146-
DescriptionKind::ShouldPanic { stdout_output } => {
147-
write!(f, "{}", String::from_utf8_lossy(stdout_output))
167+
DescriptionKind::ShouldPanic { stdout_subslice } => {
168+
write!(f, "{}", String::from_utf8_lossy(stdout_subslice.slice))
148169
}
149170
}
150171
}
@@ -166,26 +187,39 @@ pub fn heuristic_extract_description<'a>(
166187
}
167188

168189
// Try the heuristic stack trace extraction first to try and grab more information first.
169-
if let Some(stderr_output) = heuristic_stack_trace(stderr) {
170-
return Some(DescriptionKind::StackTrace { stderr_output });
190+
if let Some(stderr_subslice) = heuristic_stack_trace(stderr) {
191+
return Some(DescriptionKind::StackTrace { stderr_subslice });
171192
}
172-
if let Some(stderr_output) = heuristic_error_str(stderr) {
173-
return Some(DescriptionKind::ErrorStr { stderr_output });
193+
if let Some(stderr_subslice) = heuristic_error_str(stderr) {
194+
return Some(DescriptionKind::ErrorStr { stderr_subslice });
174195
}
175-
if let Some(stdout_output) = heuristic_should_panic(stdout) {
176-
return Some(DescriptionKind::ShouldPanic { stdout_output });
196+
if let Some(stdout_subslice) = heuristic_should_panic(stdout) {
197+
return Some(DescriptionKind::ShouldPanic { stdout_subslice });
177198
}
178199

179200
None
180201
}
181202

182-
fn heuristic_should_panic(stdout: &[u8]) -> Option<&[u8]> {
183-
stdout
203+
fn heuristic_should_panic(stdout: &[u8]) -> Option<ByteSubslice<'_>> {
204+
let line = stdout
184205
.lines()
185-
.find(|line| line.contains_str("note: test did not panic as expected"))
206+
.find(|line| line.contains_str("note: test did not panic as expected"))?;
207+
208+
// SAFETY: line is a subslice of stdout.
209+
let start = unsafe { line.as_ptr().offset_from(stdout.as_ptr()) };
210+
211+
let start = usize::try_from(start).unwrap_or_else(|error| {
212+
panic!(
213+
"negative offset from stdout.as_ptr() ({:x}) to line.as_ptr() ({:x}): {}",
214+
stdout.as_ptr() as usize,
215+
line.as_ptr() as usize,
216+
error
217+
)
218+
});
219+
Some(ByteSubslice { slice: line, start })
186220
}
187221

188-
fn heuristic_stack_trace(stderr: &[u8]) -> Option<&[u8]> {
222+
fn heuristic_stack_trace(stderr: &[u8]) -> Option<ByteSubslice<'_>> {
189223
let panicked_at_match = PANICKED_AT_REGEX.find(stderr)?;
190224
// If the previous line starts with "Error: ", grab it as well -- it contains the error with
191225
// result-based test failures.
@@ -200,17 +234,24 @@ fn heuristic_stack_trace(stderr: &[u8]) -> Option<&[u8]> {
200234
// TODO: this grabs too much -- it is possible that destructors print out further messages so we
201235
// should be more careful. But it's hard to tell what's printed by the panic and what's printed
202236
// by destructors, so we lean on the side of caution.
203-
Some(stderr[start..].trim_end_with(|c| c.is_whitespace()))
237+
Some(ByteSubslice {
238+
slice: stderr[start..].trim_end_with(|c| c.is_whitespace()),
239+
start,
240+
})
204241
}
205242

206-
fn heuristic_error_str(stderr: &[u8]) -> Option<&[u8]> {
243+
fn heuristic_error_str(stderr: &[u8]) -> Option<ByteSubslice<'_>> {
207244
// Starting Rust 1.66, Result-based errors simply print out "Error: ".
208245
let error_match = ERROR_REGEX.find(stderr)?;
209246
let start = error_match.start();
247+
210248
// TODO: this grabs too much -- it is possible that destructors print out further messages so we
211249
// should be more careful. But it's hard to tell what's printed by the error and what's printed
212250
// by destructors, so we lean on the side of caution.
213-
Some(stderr[start..].trim_end_with(|c| c.is_whitespace()))
251+
Some(ByteSubslice {
252+
slice: stderr[start..].trim_end_with(|c| c.is_whitespace()),
253+
start,
254+
})
214255
}
215256

216257
/// Given a slice, find the index of the point at which highlighting should end.
@@ -257,7 +298,14 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 13 filtered out;
257298
for (input, output) in tests {
258299
let extracted = heuristic_should_panic(input.as_bytes())
259300
.expect("should-panic message should have been found");
260-
assert_eq!(DisplayWrapper(extracted), DisplayWrapper(output.as_bytes()));
301+
assert_eq!(
302+
DisplayWrapper(extracted.slice),
303+
DisplayWrapper(output.as_bytes())
304+
);
305+
assert_eq!(
306+
extracted.start,
307+
extracted.slice.as_ptr() as usize - input.as_bytes().as_ptr() as usize
308+
);
261309
}
262310
}
263311

@@ -379,9 +427,16 @@ some more text at the end, followed by some newlines"#,
379427
];
380428

381429
for (input, output) in tests {
382-
let trace = heuristic_stack_trace(input.as_bytes())
430+
let extracted = heuristic_stack_trace(input.as_bytes())
383431
.expect("stack trace should have been found");
384-
assert_eq!(DisplayWrapper(trace), DisplayWrapper(output.as_bytes()));
432+
assert_eq!(
433+
DisplayWrapper(extracted.slice),
434+
DisplayWrapper(output.as_bytes())
435+
);
436+
assert_eq!(
437+
extracted.start,
438+
extracted.slice.as_ptr() as usize - input.as_bytes().as_ptr() as usize
439+
);
385440
}
386441
}
387442

@@ -393,9 +448,16 @@ some more text at the end, followed by some newlines"#,
393448
)];
394449

395450
for (input, output) in tests {
396-
let error_str =
451+
let extracted =
397452
heuristic_error_str(input.as_bytes()).expect("error string should have been found");
398-
assert_eq!(DisplayWrapper(error_str), DisplayWrapper(output.as_bytes()));
453+
assert_eq!(
454+
DisplayWrapper(extracted.slice),
455+
DisplayWrapper(output.as_bytes())
456+
);
457+
assert_eq!(
458+
extracted.start,
459+
extracted.slice.as_ptr() as usize - input.as_bytes().as_ptr() as usize
460+
);
399461
}
400462
}
401463

0 commit comments

Comments
 (0)