Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Fix some panics in edge cases. #185

Merged
merged 4 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,13 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {
let text_slice = span.text[0].text.chars().collect::<Vec<char>>();

// We subtract `1` because these highlights are 1-based
let start = span.text[0].highlight_start - 1;
let end = span.text[0].highlight_end - 1;
// Check the `min` so that it doesn't attempt to index out-of-bounds when
// the span points to the "end" of the line. For example, a line of
// "foo\n" with a highlight_start of 5 is intended to highlight *after*
// the line. This needs to compensate since the newline has been removed
// from the text slice.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh? Is this a change that the newlines is not part of the text slice?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, rustc has never included line endings in the "text" lines.

It is somewhat unusual for spans to point past the end of the line. no_main is one of the few ways to do it. The unclosed delimiter error is the only other way I know of.

let start = (span.text[0].highlight_start - 1).min(text_slice.len());
let end = (span.text[0].highlight_end - 1).min(text_slice.len());
let lead = text_slice[indent..start].iter().collect();
let mut body: String = text_slice[start..end].iter().collect();

Expand All @@ -122,7 +127,8 @@ fn parse_snippet(span: &DiagnosticSpan) -> Option<Snippet> {

// If we get a DiagnosticSpanLine where highlight_end > text.len(), we prevent an 'out of
// bounds' access by making sure the index is within the array bounds.
let last_tail_index = last.highlight_end.min(last.text.len()) - 1;
// `saturating_sub` is used in case of an empty file
let last_tail_index = last.highlight_end.min(last.text.len()).saturating_sub(1);
let last_slice = last.text.chars().collect::<Vec<char>>();

if span.text.len() > 1 {
Expand Down
42 changes: 42 additions & 0 deletions tests/edge-cases/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"message": "`main` function not found in crate `empty`",
"code": {
"code": "E0601",
"explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n"
},
"level": "error",
"spans": [
{
"file_name": "empty.rs",
"byte_start": 0,
"byte_end": 0,
"line_start": 0,
"line_end": 0,
"column_start": 1,
"column_end": 1,
"is_primary": true,
"text": [
{
"text": "",
"highlight_start": 1,
"highlight_end": 1
}
],
"label": null,
"suggested_replacement": null,
"suggestion_applicability": null,
"expansion": null
}
],
"children": [
{
"message": "consider adding a `main` function to `empty.rs`",
"code": null,
"level": "note",
"spans": [],
"children": [],
"rendered": null
}
],
"rendered": "error[E0601]: `main` function not found in crate `empty`\n |\n = note: consider adding a `main` function to `empty.rs`\n\n"
}
Empty file added tests/edge-cases/empty.rs
Empty file.
33 changes: 33 additions & 0 deletions tests/edge-cases/no_main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"message": "`main` function not found in crate `no_main`",
"code": {
"code": "E0601",
"explanation": "No `main` function was found in a binary crate. To fix this error, add a\n`main` function. For example:\n\n```\nfn main() {\n // Your program will start here.\n println!(\"Hello world!\");\n}\n```\n\nIf you don't know the basics of Rust, you can go look to the Rust Book to get\nstarted: https://doc.rust-lang.org/book/\n"
},
"level": "error",
"spans": [
{
"file_name": "no_main.rs",
"byte_start": 26,
"byte_end": 26,
"line_start": 1,
"line_end": 1,
"column_start": 27,
"column_end": 27,
"is_primary": true,
"text": [
{
"text": "// This file has no main.",
"highlight_start": 27,
"highlight_end": 27
}
],
"label": "consider adding a `main` function to `no_main.rs`",
"suggested_replacement": null,
"suggestion_applicability": null,
"expansion": null
}
],
"children": [],
"rendered": "error[E0601]: `main` function not found in crate `no_main`\n --> no_main.rs:1:27\n |\n1 | // This file has no main.\n | ^ consider adding a `main` function to `no_main.rs`\n\n"
}
1 change: 1 addition & 0 deletions tests/edge-cases/no_main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This file has no main.
41 changes: 18 additions & 23 deletions tests/edge_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,24 @@ use rustfix;
use std::collections::HashSet;
use std::fs;

#[test]
fn multiple_fix_options_yield_no_suggestions() {
let json = fs::read_to_string("./tests/edge-cases/skip-multi-option-lints.json").unwrap();
let expected_suggestions =
rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything)
macro_rules! expect_empty_json_test {
($name:ident, $file:expr) => {
#[test]
fn $name() {
let json = fs::read_to_string(concat!("./tests/edge-cases/", $file)).unwrap();
let expected_suggestions = rustfix::get_suggestions_from_json(
&json,
&HashSet::new(),
rustfix::Filter::Everything,
)
.unwrap();
assert!(expected_suggestions.is_empty());
assert!(expected_suggestions.is_empty());
}
};
}

#[test]
fn out_of_bounds_test() {
let json = fs::read_to_string("./tests/edge-cases/out_of_bounds.recorded.json").unwrap();
let expected_suggestions =
rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything)
.unwrap();
assert!(expected_suggestions.is_empty());
}

#[test]
fn utf8_identifiers_test() {
let json = fs::read_to_string("./tests/edge-cases/utf8_idents.recorded.json").unwrap();
let expected_suggestions =
rustfix::get_suggestions_from_json(&json, &HashSet::new(), rustfix::Filter::Everything)
.unwrap();
assert!(expected_suggestions.is_empty());
}
expect_empty_json_test! {multiple_fix_options_yield_no_suggestions, "skip-multi-option-lints.json"}
expect_empty_json_test! {out_of_bounds_test, "out_of_bounds.recorded.json"}
expect_empty_json_test! {utf8_identifiers_test, "utf8_idents.recorded.json"}
expect_empty_json_test! {empty, "empty.json"}
expect_empty_json_test! {no_main, "no_main.json"}
5 changes: 4 additions & 1 deletion tests/parse_and_replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ fn compile(file: &Path, mode: &str) -> Result<Output, Error> {
fn compile_and_get_json_errors(file: &Path, mode: &str) -> Result<String, Error> {
let res = compile(file, mode)?;
let stderr = String::from_utf8(res.stderr)?;
if stderr.contains("is only accepted on the nightly compiler") {
panic!("rustfix tests require a nightly compiler");
}

match res.status.code() {
Some(0) | Some(1) | Some(101) => Ok(stderr),
Expand Down Expand Up @@ -160,7 +163,7 @@ fn test_rustfix_with_file<P: AsRef<Path>>(file: P, mode: &str) -> Result<(), Err
))?;
let expected_suggestions =
rustfix::get_suggestions_from_json(&expected_json, &HashSet::new(), filter_suggestions)
.context("could not load expected suggesitons")?;
.context("could not load expected suggestions")?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch :)


ensure!(
expected_suggestions == suggestions,
Expand Down