4930: Avoid all unchecked indexing in match checking r=flodiebold a=jonas-schievink

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/4416, but replaces it with a false positive.

r? @flodiebold 

Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
This commit is contained in:
bors[bot] 2020-06-18 19:43:05 +00:00 committed by GitHub
commit 902a9c6da7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -312,20 +312,16 @@ impl PatStack {
Self(v) Self(v)
} }
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn head(&self) -> PatIdOrWild {
self.0[0]
}
fn get_head(&self) -> Option<PatIdOrWild> { fn get_head(&self) -> Option<PatIdOrWild> {
self.0.first().copied() self.0.first().copied()
} }
fn tail(&self) -> &[PatIdOrWild] {
self.0.get(1..).unwrap_or(&[])
}
fn to_tail(&self) -> PatStack { fn to_tail(&self) -> PatStack {
Self::from_slice(&self.0[1..]) Self::from_slice(self.tail())
} }
fn replace_head_with<I, T>(&self, pats: I) -> PatStack fn replace_head_with<I, T>(&self, pats: I) -> PatStack
@ -347,7 +343,7 @@ impl PatStack {
/// ///
/// See the module docs and the associated documentation in rustc for details. /// See the module docs and the associated documentation in rustc for details.
fn specialize_wildcard(&self, cx: &MatchCheckCtx) -> Option<PatStack> { fn specialize_wildcard(&self, cx: &MatchCheckCtx) -> Option<PatStack> {
if matches!(self.head().as_pat(cx), Pat::Wild) { if matches!(self.get_head()?.as_pat(cx), Pat::Wild) {
Some(self.to_tail()) Some(self.to_tail())
} else { } else {
None None
@ -362,11 +358,12 @@ impl PatStack {
cx: &MatchCheckCtx, cx: &MatchCheckCtx,
constructor: &Constructor, constructor: &Constructor,
) -> MatchCheckResult<Option<PatStack>> { ) -> MatchCheckResult<Option<PatStack>> {
if self.is_empty() { let head = match self.get_head() {
return Ok(None); Some(head) => head,
} None => return Ok(None),
};
let head_pat = self.head().as_pat(cx); let head_pat = head.as_pat(cx);
let result = match (head_pat, constructor) { let result = match (head_pat, constructor) {
(Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => {
if ellipsis.is_some() { if ellipsis.is_some() {
@ -394,7 +391,7 @@ impl PatStack {
(Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?),
(Pat::Path(_), Constructor::Enum(constructor)) => { (Pat::Path(_), Constructor::Enum(constructor)) => {
// unit enum variants become `Pat::Path` // unit enum variants become `Pat::Path`
let pat_id = self.head().as_id().expect("we know this isn't a wild"); let pat_id = head.as_id().expect("we know this isn't a wild");
if !enum_variant_matches(cx, pat_id, *constructor) { if !enum_variant_matches(cx, pat_id, *constructor) {
None None
} else { } else {
@ -405,7 +402,7 @@ impl PatStack {
Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, Pat::TupleStruct { args: ref pat_ids, ellipsis, .. },
Constructor::Enum(enum_constructor), Constructor::Enum(enum_constructor),
) => { ) => {
let pat_id = self.head().as_id().expect("we know this isn't a wild"); let pat_id = head.as_id().expect("we know this isn't a wild");
if !enum_variant_matches(cx, pat_id, *enum_constructor) { if !enum_variant_matches(cx, pat_id, *enum_constructor) {
None None
} else { } else {
@ -445,7 +442,7 @@ impl PatStack {
} }
} }
(Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => { (Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => {
let pat_id = self.head().as_id().expect("we know this isn't a wild"); let pat_id = head.as_id().expect("we know this isn't a wild");
if !enum_variant_matches(cx, pat_id, *e) { if !enum_variant_matches(cx, pat_id, *e) {
None None
} else { } else {
@ -491,7 +488,7 @@ impl PatStack {
) -> MatchCheckResult<PatStack> { ) -> MatchCheckResult<PatStack> {
assert_eq!( assert_eq!(
Pat::Wild, Pat::Wild,
self.head().as_pat(cx), self.get_head().expect("expand_wildcard called on empty PatStack").as_pat(cx),
"expand_wildcard must only be called on PatStack with wild at head", "expand_wildcard must only be called on PatStack with wild at head",
); );
@ -509,7 +506,6 @@ impl PatStack {
} }
} }
#[derive(Debug)]
/// A collection of PatStack. /// A collection of PatStack.
/// ///
/// This type is modeled from the struct of the same name in `rustc`. /// This type is modeled from the struct of the same name in `rustc`.
@ -623,13 +619,16 @@ pub(crate) fn is_useful(
_ => (), _ => (),
} }
if v.is_empty() { let head = match v.get_head() {
let result = if matrix.is_empty() { Usefulness::Useful } else { Usefulness::NotUseful }; Some(head) => head,
None => {
let result = if matrix.is_empty() { Usefulness::Useful } else { Usefulness::NotUseful };
return Ok(result); return Ok(result);
} }
};
if let Pat::Or(pat_ids) = v.head().as_pat(cx) { if let Pat::Or(pat_ids) = head.as_pat(cx) {
let mut found_unimplemented = false; let mut found_unimplemented = false;
let any_useful = pat_ids.iter().any(|&pat_id| { let any_useful = pat_ids.iter().any(|&pat_id| {
let v = PatStack::from_pattern(pat_id); let v = PatStack::from_pattern(pat_id);
@ -653,7 +652,7 @@ pub(crate) fn is_useful(
}; };
} }
if let Some(constructor) = pat_constructor(cx, v.head())? { if let Some(constructor) = pat_constructor(cx, head)? {
let matrix = matrix.specialize_constructor(&cx, &constructor)?; let matrix = matrix.specialize_constructor(&cx, &constructor)?;
let v = v let v = v
.specialize_constructor(&cx, &constructor)? .specialize_constructor(&cx, &constructor)?
@ -854,10 +853,10 @@ mod tests {
} }
pub(super) fn check_no_diagnostic(ra_fixture: &str) { pub(super) fn check_no_diagnostic(ra_fixture: &str) {
let diagnostic_count = let (s, diagnostic_count) =
TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>().1; TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>();
assert_eq!(0, diagnostic_count, "expected no diagnostic, found one"); assert_eq!(0, diagnostic_count, "expected no diagnostic, found one: {}", s);
} }
#[test] #[test]
@ -2014,6 +2013,28 @@ mod tests {
", ",
); );
} }
#[test]
fn or_pattern_panic_2() {
// FIXME: This is a false positive, but the code used to cause a panic in the match checker,
// so this acts as a regression test for that.
check_diagnostic(
r"
pub enum Category {
Infinity,
Zero,
}
fn panic(a: Category, b: Category) {
match (a, b) {
(Category::Infinity, Category::Infinity) | (Category::Zero, Category::Zero) => {}
(Category::Infinity | Category::Zero, _) => {}
}
}
",
);
}
} }
#[cfg(test)] #[cfg(test)]