Skip to content

Commit d6c60a6

Browse files
committed
Add lint for using a type with a destructor in a zero-length repeat expr
Currently, writing a zero-length array repeat expression (e.g. `[String::new(); 0]`) will cause the initializer value to be leaked. See rust-lang#74836 for more details There are three ways that we could potentially handle this case: 1. Run the destructor immediately after 'constructing' the zero-length array. 2. Run the destructor when the initializer value would otherwise be dropped (i.e. at the end of the lexical scope) 3. Keep the current behavior and continue to leak to the initializer Note that the 'obvious' choice (running the destructor when the array is dropped) does not work, since a zero-length array does not actually store the value we want to drop. All of these options seem likely to be surprising and inconsistent to users. Since any fully monomorphic constant can be used as the repeat length, this has the potential to cause confusing 'action at a distance' (e.g. changing a `const` from 0 to 1 results in drop order changing). Regardless of which option we decide on, we should tell users to use an empty array (`[]`) instead. This commit adds a new lint ZERO_REPEAT_WITH_DROP, which fires when a type that (potentially) has a destructor is used in a zero-length array repeat expression. If a type has no drop glue, we skip linting, since dropping it has no user-visible side effects. If we do not know if a type has drop glue (e.g. `Option<T>`), we lint anyway, since some choice of generic arguments could trigger the strange drop behavior. If the length const expression is not fully monomorphic (e.g. contains a generic parameter), the compiler requires the type used in the repeat expression to be `Copy`. Therefore, we don't need to lint in this case, since a `Copy` type can never have drop glue.
1 parent 9be8ffc commit d6c60a6

File tree

4 files changed

+155
-2
lines changed

4 files changed

+155
-2
lines changed

src/librustc_mir/borrow_check/type_check/mod.rs

+33-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use either::Either;
77

88
use rustc_data_structures::frozen::Frozen;
99
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10-
use rustc_errors::struct_span_err;
10+
use rustc_errors::{struct_span_err, Applicability};
1111
use rustc_hir as hir;
1212
use rustc_hir::def_id::{DefId, LocalDefId};
1313
use rustc_hir::lang_items::{CoerceUnsizedTraitLangItem, CopyTraitLangItem, SizedTraitLangItem};
@@ -30,6 +30,7 @@ use rustc_middle::ty::{
3030
self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, RegionVid, ToPredicate, Ty,
3131
TyCtxt, UserType, UserTypeAnnotationIndex, WithConstness,
3232
};
33+
use rustc_session::lint::builtin::ZERO_REPEAT_WITH_DROP;
3334
use rustc_span::{Span, DUMMY_SP};
3435
use rustc_target::abi::VariantIdx;
3536
use rustc_trait_selection::infer::InferCtxtExt as _;
@@ -1993,7 +1994,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
19931994
// than 1.
19941995
// If the length is larger than 1, the repeat expression will need to copy the
19951996
// element, so we require the `Copy` trait.
1996-
if len.try_eval_usize(tcx, self.param_env).map_or(true, |len| len > 1) {
1997+
let len_val = len.try_eval_usize(tcx, self.param_env);
1998+
if len_val.map_or(true, |len| len > 1) {
19971999
if let Operand::Move(_) = operand {
19982000
// While this is located in `nll::typeck` this error is not an NLL error, it's
19992001
// a required check to make sure that repeated elements implement `Copy`.
@@ -2038,6 +2040,35 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
20382040
);
20392041
}
20402042
}
2043+
} else if let Some(0) = len_val {
2044+
let ty = operand.ty(body, tcx);
2045+
if ty.needs_drop(tcx, self.param_env) {
2046+
let source_info = body.source_info(location);
2047+
let lint_root = match &body.source_scopes[source_info.scope].local_data {
2048+
ClearCrossCrate::Set(data) => data.lint_root,
2049+
_ => tcx.hir().as_local_hir_id(self.mir_def_id),
2050+
};
2051+
2052+
tcx.struct_span_lint_hir(
2053+
ZERO_REPEAT_WITH_DROP,
2054+
lint_root,
2055+
source_info.span,
2056+
|lint| {
2057+
lint
2058+
.build("used a type with a destructor in a zero-length repeat expression")
2059+
.note(&format!("the value used here has type `{}`, which may have a destructor", ty))
2060+
.note("a length of zero is used, which will cause the value to be dropped in a strange location")
2061+
.span_suggestion(
2062+
source_info.span,
2063+
"consider using an empty array expression",
2064+
"[]".to_string(),
2065+
Applicability::MaybeIncorrect
2066+
)
2067+
.emit();
2068+
2069+
}
2070+
);
2071+
}
20412072
}
20422073
}
20432074

src/librustc_session/lint/builtin.rs

+7
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,12 @@ declare_lint! {
549549
};
550550
}
551551

552+
declare_lint! {
553+
pub ZERO_REPEAT_WITH_DROP,
554+
Warn,
555+
"detects using a type with a destructor in an zero-length array repeat expression"
556+
}
557+
552558
declare_lint_pass! {
553559
/// Does nothing as a lint pass, but registers some `Lint`s
554560
/// that are used by other parts of the compiler.
@@ -623,6 +629,7 @@ declare_lint_pass! {
623629
UNSAFE_OP_IN_UNSAFE_FN,
624630
INCOMPLETE_INCLUDE,
625631
CENUM_IMPL_DROP_CAST,
632+
ZERO_REPEAT_WITH_DROP,
626633
]
627634
}
628635

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#![feature(const_generics)]
2+
#![allow(incomplete_features)]
3+
#![deny(zero_repeat_with_drop)]
4+
5+
const ZERO: usize = 1 * 0;
6+
7+
const fn make_val<T>(val: T) -> T { val }
8+
9+
struct NoDropOrCopy;
10+
struct WithDropGlue(String);
11+
12+
fn foo<T, V: Copy, F: Fn() -> T, const N: usize>(not_copy: F, copy: V) {
13+
// All of these should triger the lint
14+
let _ = [not_copy(); 0]; //~ ERROR used a type
15+
let _ = [not_copy(); 1 - 1]; //~ ERROR used a type
16+
let _ = [not_copy(); ZERO]; //~ ERROR used a type
17+
let _ = [Some(not_copy()); 0]; //~ ERROR used a type
18+
let _ = [None::<T>; 0]; //~ ERROR used a type
19+
let _ = [make_val(not_copy()); 0]; //~ ERROR used a type
20+
let _ = [String::new(); 0]; //~ ERROR used a type
21+
let _ = [WithDropGlue(String::new()); 0]; //~ ERROR used a type
22+
23+
// None of these should trigger the lint
24+
let _ = [copy; 0];
25+
let _ = [Some(copy); 0];
26+
let _ = [NoDropOrCopy; 0];
27+
let _ = [not_copy(); 1];
28+
let _ = [copy; N];
29+
}
30+
31+
fn allow_it() {
32+
#[allow(zero_repeat_with_drop)]
33+
let _ = [WithDropGlue(String::new()); 1 - 1];
34+
}
35+
36+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
error: used a type with a destructor in a zero-length repeat expression
2+
--> $DIR/lint-zero-repeat-with-drop.rs:14:13
3+
|
4+
LL | let _ = [not_copy(); 0];
5+
| ^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/lint-zero-repeat-with-drop.rs:3:9
9+
|
10+
LL | #![deny(zero_repeat_with_drop)]
11+
| ^^^^^^^^^^^^^^^^^^^^^
12+
= note: the value used here has type `T`, which may have a destructor
13+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
14+
15+
error: used a type with a destructor in a zero-length repeat expression
16+
--> $DIR/lint-zero-repeat-with-drop.rs:15:13
17+
|
18+
LL | let _ = [not_copy(); 1 - 1];
19+
| ^^^^^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
20+
|
21+
= note: the value used here has type `T`, which may have a destructor
22+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
23+
24+
error: used a type with a destructor in a zero-length repeat expression
25+
--> $DIR/lint-zero-repeat-with-drop.rs:16:13
26+
|
27+
LL | let _ = [not_copy(); ZERO];
28+
| ^^^^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
29+
|
30+
= note: the value used here has type `T`, which may have a destructor
31+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
32+
33+
error: used a type with a destructor in a zero-length repeat expression
34+
--> $DIR/lint-zero-repeat-with-drop.rs:17:13
35+
|
36+
LL | let _ = [Some(not_copy()); 0];
37+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
38+
|
39+
= note: the value used here has type `std::option::Option<T>`, which may have a destructor
40+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
41+
42+
error: used a type with a destructor in a zero-length repeat expression
43+
--> $DIR/lint-zero-repeat-with-drop.rs:18:13
44+
|
45+
LL | let _ = [None::<T>; 0];
46+
| ^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
47+
|
48+
= note: the value used here has type `std::option::Option<T>`, which may have a destructor
49+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
50+
51+
error: used a type with a destructor in a zero-length repeat expression
52+
--> $DIR/lint-zero-repeat-with-drop.rs:19:13
53+
|
54+
LL | let _ = [make_val(not_copy()); 0];
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
56+
|
57+
= note: the value used here has type `T`, which may have a destructor
58+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
59+
60+
error: used a type with a destructor in a zero-length repeat expression
61+
--> $DIR/lint-zero-repeat-with-drop.rs:20:13
62+
|
63+
LL | let _ = [String::new(); 0];
64+
| ^^^^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
65+
|
66+
= note: the value used here has type `std::string::String`, which may have a destructor
67+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
68+
69+
error: used a type with a destructor in a zero-length repeat expression
70+
--> $DIR/lint-zero-repeat-with-drop.rs:21:13
71+
|
72+
LL | let _ = [WithDropGlue(String::new()); 0];
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an empty array expression: `[]`
74+
|
75+
= note: the value used here has type `WithDropGlue`, which may have a destructor
76+
= note: a length of zero is used, which will cause the value to be dropped in a strange location
77+
78+
error: aborting due to 8 previous errors
79+

0 commit comments

Comments
 (0)