// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934 //@no-rustfix #![warn(clippy::significant_drop_in_scrutinee)] #![allow(dead_code, unused_assignments)] #![allow(clippy::match_single_binding, clippy::single_match, clippy::uninlined_format_args)] use std::num::ParseIntError; use std::ops::Deref; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Mutex, MutexGuard, RwLock}; struct State {} impl State { fn foo(&self) -> bool { true } fn bar(&self) {} } fn should_not_trigger_lint_with_mutex_guard_outside_match() { let mutex = Mutex::new(State {}); // Should not trigger lint because the temporary should drop at the `;` on line before the match let is_foo = mutex.lock().unwrap().foo(); match is_foo { true => { mutex.lock().unwrap().bar(); }, false => {}, }; } fn should_not_trigger_lint_with_mutex_guard_when_taking_ownership_in_match() { let mutex = Mutex::new(State {}); // Should not trigger lint because the scrutinee is explicitly returning the MutexGuard, // so its lifetime should not be surprising. match mutex.lock() { Ok(guard) => { guard.foo(); mutex.lock().unwrap().bar(); }, _ => {}, }; } fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() { let mutex = Mutex::new(State {}); // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it // is preserved until the end of the match, but there is no clear indication that this is the // case. match mutex.lock().unwrap().foo() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => { mutex.lock().unwrap().bar(); }, false => {}, }; } fn should_not_trigger_lint_with_mutex_guard_in_match_scrutinee_when_lint_allowed() { let mutex = Mutex::new(State {}); // Lint should not be triggered because it is "allowed" below. #[allow(clippy::significant_drop_in_scrutinee)] match mutex.lock().unwrap().foo() { true => { mutex.lock().unwrap().bar(); }, false => {}, }; } fn should_not_trigger_lint_for_insignificant_drop() { // Should not trigger lint because there are no temporaries whose drops have a significant // side effect. match 1u64.to_string().is_empty() { true => { println!("It was empty") }, false => { println!("It was not empty") }, } } struct StateWithMutex { m: Mutex, } struct MutexGuardWrapper<'a> { mg: MutexGuard<'a, u64>, } impl<'a> MutexGuardWrapper<'a> { fn get_the_value(&self) -> u64 { *self.mg.deref() } } struct MutexGuardWrapperWrapper<'a> { mg: MutexGuardWrapper<'a>, } impl<'a> MutexGuardWrapperWrapper<'a> { fn get_the_value(&self) -> u64 { *self.mg.mg.deref() } } impl StateWithMutex { fn lock_m(&self) -> MutexGuardWrapper<'_> { MutexGuardWrapper { mg: self.m.lock().unwrap(), } } fn lock_m_m(&self) -> MutexGuardWrapperWrapper<'_> { MutexGuardWrapperWrapper { mg: MutexGuardWrapper { mg: self.m.lock().unwrap(), }, } } fn foo(&self) -> bool { true } fn bar(&self) {} } fn should_trigger_lint_with_wrapped_mutex() { let s = StateWithMutex { m: Mutex::new(1) }; // Should trigger lint because a temporary contains a type with a significant drop and its // lifetime is not obvious. Additionally, it is not obvious from looking at the scrutinee that // the temporary contains such a type, making it potentially even more surprising. match s.lock_m().get_the_value() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior 1 => { println!("Got 1. Is it still 1?"); println!("{}", s.lock_m().get_the_value()); }, 2 => { println!("Got 2. Is it still 2?"); println!("{}", s.lock_m().get_the_value()); }, _ => {}, } println!("All done!"); } fn should_trigger_lint_with_double_wrapped_mutex() { let s = StateWithMutex { m: Mutex::new(1) }; // Should trigger lint because a temporary contains a type which further contains a type with a // significant drop and its lifetime is not obvious. Additionally, it is not obvious from // looking at the scrutinee that the temporary contains such a type, making it potentially even // more surprising. match s.lock_m_m().get_the_value() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior 1 => { println!("Got 1. Is it still 1?"); println!("{}", s.lock_m().get_the_value()); }, 2 => { println!("Got 2. Is it still 2?"); println!("{}", s.lock_m().get_the_value()); }, _ => {}, } println!("All done!"); } struct Counter { i: AtomicU64, } #[clippy::has_significant_drop] struct CounterWrapper<'a> { counter: &'a Counter, } impl<'a> CounterWrapper<'a> { fn new(counter: &Counter) -> CounterWrapper { counter.i.fetch_add(1, Ordering::Relaxed); CounterWrapper { counter } } } impl<'a> Drop for CounterWrapper<'a> { fn drop(&mut self) { self.counter.i.fetch_sub(1, Ordering::Relaxed); } } impl Counter { fn temp_increment(&self) -> Vec { vec![CounterWrapper::new(self), CounterWrapper::new(self)] } } fn should_trigger_lint_for_vec() { let counter = Counter { i: AtomicU64::new(0) }; // Should trigger lint because the temporary in the scrutinee returns a collection of types // which have significant drops. The types with significant drops are also non-obvious when // reading the expression in the scrutinee. match counter.temp_increment().len() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior 2 => { let current_count = counter.i.load(Ordering::Relaxed); println!("Current count {}", current_count); assert_eq!(current_count, 0); }, 1 => {}, 3 => {}, _ => {}, }; } struct StateWithField { s: String, } // Should trigger lint only on the type in the tuple which is created using a temporary // with a significant drop. Additionally, this test ensures that the format of the tuple // is preserved correctly in the suggestion. fn should_trigger_lint_for_tuple_in_scrutinee() { let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); { match (mutex1.lock().unwrap().s.len(), true) { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until //~| NOTE: this might lead to deadlocks or other unexpected behavior (3, _) => { println!("started"); mutex1.lock().unwrap().s.len(); println!("done"); }, (_, _) => {}, }; match (true, mutex1.lock().unwrap().s.len(), true) { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until //~| NOTE: this might lead to deadlocks or other unexpected behavior (_, 3, _) => { println!("started"); mutex1.lock().unwrap().s.len(); println!("done"); }, (_, _, _) => {}, }; let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until //~| NOTE: this might lead to deadlocks or other unexpected behavior //~| ERROR: temporary with significant `Drop` in `match` scrutinee will live until //~| NOTE: this might lead to deadlocks or other unexpected behavior (3, _, 3) => { println!("started"); mutex1.lock().unwrap().s.len(); mutex2.lock().unwrap().s.len(); println!("done"); }, (_, _, _) => {}, }; } } // Should not trigger lint since `String::as_str` returns a reference (i.e., `&str`) // to the locked data (i.e., the `String`) and it is not surprising that matching such // a reference needs to keep the data locked until the end of the match block. fn should_not_trigger_lint_for_string_as_str() { let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); { let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() }); match mutex3.lock().unwrap().s.as_str() { "three" => { println!("started"); mutex1.lock().unwrap().s.len(); mutex2.lock().unwrap().s.len(); println!("done"); }, _ => {}, }; match (true, mutex3.lock().unwrap().s.as_str()) { (_, "three") => { println!("started"); mutex1.lock().unwrap().s.len(); mutex2.lock().unwrap().s.len(); println!("done"); }, (_, _) => {}, }; } } // Should trigger lint when either side of a binary operation creates a temporary with a // significant drop. // To avoid potential unnecessary copies or creating references that would trigger the significant // drop problem, the lint recommends moving the entire binary operation. fn should_trigger_lint_for_accessing_field_in_mutex_in_one_side_of_binary_op() { let mutex = Mutex::new(StateWithField { s: "state".to_owned() }); match mutex.lock().unwrap().s.len() > 1 { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => { mutex.lock().unwrap().s.len(); }, false => {}, }; match 1 < mutex.lock().unwrap().s.len() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => { mutex.lock().unwrap().s.len(); }, false => {}, }; } // Should trigger lint when both sides of a binary operation creates a temporary with a // significant drop. // To avoid potential unnecessary copies or creating references that would trigger the significant // drop problem, the lint recommends moving the entire binary operation. fn should_trigger_lint_for_accessing_fields_in_mutex_in_both_sides_of_binary_op() { let mutex1 = Mutex::new(StateWithField { s: "state".to_owned() }); let mutex2 = Mutex::new(StateWithField { s: "statewithfield".to_owned(), }); match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => { println!( "{} < {}", mutex1.lock().unwrap().s.len(), mutex2.lock().unwrap().s.len() ); }, false => {}, }; match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => { println!( "{} >= {}", mutex1.lock().unwrap().s.len(), mutex2.lock().unwrap().s.len() ); }, false => {}, }; } fn should_not_trigger_lint_for_closure_in_scrutinee() { let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); let get_mutex_guard = || mutex1.lock().unwrap().s.len(); // Should not trigger lint because the temporary with a significant drop will be dropped // at the end of the closure, so the MutexGuard will be unlocked and not have a potentially // surprising lifetime. match get_mutex_guard() > 1 { true => { mutex1.lock().unwrap().s.len(); }, false => {}, }; } fn should_trigger_lint_for_return_from_closure_in_scrutinee() { let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); let get_mutex_guard = || mutex1.lock().unwrap(); // Should trigger lint because the temporary with a significant drop is returned from the // closure but not used directly in any match arms, so it has a potentially surprising lifetime. match get_mutex_guard().s.len() > 1 { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => { mutex1.lock().unwrap().s.len(); }, false => {}, }; } fn should_trigger_lint_for_return_from_match_in_scrutinee() { let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); let i = 100; // Should trigger lint because the nested match within the scrutinee returns a temporary with a // significant drop is but not used directly in any match arms, so it has a potentially // surprising lifetime. match match i { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior 100 => mutex1.lock().unwrap(), _ => mutex2.lock().unwrap(), } .s .len() > 1 { true => { mutex1.lock().unwrap().s.len(); }, false => { println!("nothing to do here"); }, }; } fn should_trigger_lint_for_return_from_if_in_scrutinee() { let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); let i = 100; // Should trigger lint because the nested if-expression within the scrutinee returns a temporary // with a significant drop is but not used directly in any match arms, so it has a potentially // surprising lifetime. match if i > 1 { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior mutex1.lock().unwrap() } else { mutex2.lock().unwrap() } .s .len() > 1 { true => { mutex1.lock().unwrap().s.len(); }, false => {}, }; } fn should_not_trigger_lint_for_if_in_scrutinee() { let mutex = Mutex::new(StateWithField { s: "state".to_owned() }); let i = 100; // Should not trigger the lint because the temporary with a significant drop *is* dropped within // the body of the if-expression nested within the match scrutinee, and therefore does not have // a potentially surprising lifetime. match if i > 1 { mutex.lock().unwrap().s.len() > 1 } else { false } { true => { mutex.lock().unwrap().s.len(); }, false => {}, }; } struct StateWithBoxedMutexGuard { u: Mutex, } impl StateWithBoxedMutexGuard { fn new() -> StateWithBoxedMutexGuard { StateWithBoxedMutexGuard { u: Mutex::new(42) } } fn lock(&self) -> Box> { Box::new(self.u.lock().unwrap()) } } fn should_trigger_lint_for_boxed_mutex_guard() { let s = StateWithBoxedMutexGuard::new(); // Should trigger lint because a temporary Box holding a type with a significant drop in a match // scrutinee may have a potentially surprising lifetime. match s.lock().deref().deref() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior 0 | 1 => println!("Value was less than 2"), _ => println!("Value is {}", s.lock().deref()), }; } struct StateStringWithBoxedMutexGuard { s: Mutex, } impl StateStringWithBoxedMutexGuard { fn new() -> StateStringWithBoxedMutexGuard { StateStringWithBoxedMutexGuard { s: Mutex::new("A String".to_owned()), } } fn lock(&self) -> Box> { Box::new(self.s.lock().unwrap()) } } fn should_not_trigger_lint_for_string_ref() { let s = StateStringWithBoxedMutexGuard::new(); let matcher = String::from("A String"); // Should not trigger lint because the second `deref` returns a string reference (`&String`). // So it is not surprising that matching the reference implies that the lock needs to be held // until the end of the block. match s.lock().deref().deref() { matcher => println!("Value is {}", s.lock().deref()), _ => println!("Value was not a match"), }; } struct StateWithIntField { i: u64, } // Should trigger lint when either side of an assign expression contains a temporary with a // significant drop, because the temporary's lifetime will be extended to the end of the match. // To avoid potential unnecessary copies or creating references that would trigger the significant // drop problem, the lint recommends moving the entire binary operation. fn should_trigger_lint_in_assign_expr() { let mutex = Mutex::new(StateWithIntField { i: 10 }); let mut i = 100; match mutex.lock().unwrap().i = i { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior _ => { println!("{}", mutex.lock().unwrap().i); }, }; match i = mutex.lock().unwrap().i { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior _ => { println!("{}", mutex.lock().unwrap().i); }, }; match mutex.lock().unwrap().i += 1 { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior _ => { println!("{}", mutex.lock().unwrap().i); }, }; match i += mutex.lock().unwrap().i { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior _ => { println!("{}", mutex.lock().unwrap().i); }, }; } #[derive(Debug)] enum RecursiveEnum { Foo(Option>), } #[derive(Debug)] enum GenericRecursiveEnum { Foo(T, Option>>), } fn should_not_cause_stack_overflow() { // Test that when a type recursively contains itself, a stack overflow does not occur when // checking sub-types for significant drops. let f = RecursiveEnum::Foo(Some(Box::new(RecursiveEnum::Foo(None)))); match f { RecursiveEnum::Foo(Some(f)) => { println!("{:?}", f) }, RecursiveEnum::Foo(f) => { println!("{:?}", f) }, } let f = GenericRecursiveEnum::Foo(1u64, Some(Box::new(GenericRecursiveEnum::Foo(2u64, None)))); match f { GenericRecursiveEnum::Foo(i, Some(f)) => { println!("{} {:?}", i, f) }, GenericRecursiveEnum::Foo(i, f) => { println!("{} {:?}", i, f) }, } } fn should_not_produce_lint_for_try_desugar() -> Result { // TryDesugar (i.e. using `?` for a Result type) will turn into a match but is out of scope // for this lint let rwlock = RwLock::new("1".to_string()); let result = rwlock.read().unwrap().parse::()?; println!("{}", result); rwlock.write().unwrap().push('2'); Ok(result) } struct ResultReturner { s: String, } impl ResultReturner { fn to_number(&self) -> Result { self.s.parse::() } } fn should_trigger_lint_for_non_ref_move_and_clone_suggestion() { let rwlock = RwLock::::new(ResultReturner { s: "1".to_string() }); match rwlock.read().unwrap().to_number() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior Ok(n) => println!("Converted to number: {}", n), Err(e) => println!("Could not convert {} to number", e), }; } fn should_not_trigger_lint_for_read_write_lock_for_loop() { let rwlock = RwLock::>::new(vec!["1".to_string()]); // Should not trigger lint. Since we're iterating over the data, it's obvious that the lock // has to be held until the iteration finishes. // https://github.com/rust-lang/rust-clippy/issues/8987 for s in rwlock.read().unwrap().iter() { println!("{}", s); } } fn do_bar(mutex: &Mutex) { mutex.lock().unwrap().bar(); } fn should_trigger_lint_without_significant_drop_in_arm() { let mutex = Mutex::new(State {}); // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it // is preserved until the end of the match, but there is no clear indication that this is the // case. match mutex.lock().unwrap().foo() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior true => do_bar(&mutex), false => {}, }; } fn should_not_trigger_on_significant_iterator_drop() { let lines = std::io::stdin().lines(); for line in lines { println!("foo: {}", line.unwrap()); } } // https://github.com/rust-lang/rust-clippy/issues/9072 fn should_not_trigger_lint_if_place_expr_has_significant_drop() { let x = Mutex::new(vec![1, 2, 3]); let x_guard = x.lock().unwrap(); match x_guard[0] { 1 => println!("1!"), x => println!("{x}"), } match x_guard.len() { 1 => println!("1!"), x => println!("{x}"), } } struct Guard<'a, T>(MutexGuard<'a, T>); struct Ref<'a, T>(&'a T); impl<'a, T> Guard<'a, T> { fn guard(&self) -> &MutexGuard { &self.0 } fn guard_ref(&self) -> Ref> { Ref(&self.0) } fn take(self) -> Box> { Box::new(self.0) } } fn should_not_trigger_for_significant_drop_ref() { let mutex = Mutex::new(vec![1, 2]); let guard = Guard(mutex.lock().unwrap()); match guard.guard().len() { 0 => println!("empty"), _ => println!("not empty"), } match guard.guard_ref().0.len() { 0 => println!("empty"), _ => println!("not empty"), } match guard.take().len() { //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior 0 => println!("empty"), _ => println!("not empty"), }; } struct Foo<'a>(&'a Vec); impl<'a> Foo<'a> { fn copy_old_lifetime(&self) -> &'a Vec { self.0 } fn reborrow_new_lifetime(&self) -> &Vec { self.0 } } fn should_trigger_lint_if_and_only_if_lifetime_is_irrelevant() { let vec = Vec::new(); let mutex = Mutex::new(Foo(&vec)); // Should trigger lint even if `copy_old_lifetime()` has a lifetime, as the lifetime of // `&vec` is unrelated to the temporary with significant drop (i.e., the `MutexGuard`). for val in mutex.lock().unwrap().copy_old_lifetime() { //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior println!("{}", val); } // Should not trigger lint because `reborrow_new_lifetime()` has a lifetime and the // lifetime is related to the temporary with significant drop (i.e., the `MutexGuard`). for val in mutex.lock().unwrap().reborrow_new_lifetime() { println!("{}", val); } } fn should_not_trigger_lint_for_complex_lifetime() { let mutex = Mutex::new(vec!["hello".to_owned()]); let string = "world".to_owned(); // Should not trigger lint due to the relevant lifetime. for c in mutex.lock().unwrap().first().unwrap().chars() { println!("{}", c); } // Should trigger lint due to the irrelevant lifetime. // // FIXME: The lifetime is too complex to analyze. In order to avoid false positives, we do not // trigger lint. for c in mutex.lock().unwrap().first().map(|_| &string).unwrap().chars() { println!("{}", c); } } fn should_not_trigger_lint_with_explicit_drop() { let mutex = Mutex::new(vec![1]); // Should not trigger lint since the drop explicitly happens. for val in [drop(mutex.lock().unwrap()), ()] { println!("{:?}", val); } // Should trigger lint if there is no explicit drop. for val in [mutex.lock().unwrap()[0], 2] { //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the //~| NOTE: this might lead to deadlocks or other unexpected behavior println!("{:?}", val); } } fn main() {}