resolve: disallow label use through closure/async

This commit modifies resolve to disallow `break`/`continue` to labels
through closures or async blocks. This doesn't make sense and should
have been prohibited anyway.

Signed-off-by: David Wood <david@davidtw.co>
This commit is contained in:
David Wood 2020-06-25 15:16:38 +01:00
parent b7856f695d
commit cb541dc12c
No known key found for this signature in database
GPG Key ID: 2592E76C87381FD9
26 changed files with 428 additions and 121 deletions

View File

@ -448,6 +448,7 @@ E0763: include_str!("./error_codes/E0763.md"),
E0764: include_str!("./error_codes/E0764.md"),
E0765: include_str!("./error_codes/E0765.md"),
E0766: include_str!("./error_codes/E0766.md"),
E0767: include_str!("./error_codes/E0767.md"),
;
// E0006, // merged with E0005
// E0008, // cannot bind by-move into a pattern guard

View File

@ -0,0 +1,20 @@
An unreachable label was used.
Erroneous code example:
```compile_fail,E0767
'a: loop {
|| {
loop { break 'a } // error: use of unreachable label `'a`
};
}
```
Ensure that the label is within scope. Labels are not reachable through
functions, closures, async blocks or modules. Example:
```
'a: loop {
break 'a; // ok!
}
```

View File

@ -1123,16 +1123,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
match target {
Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
None => {
// FIXME: This should have been checked earlier. Once this is fixed,
// replace with `delay_span_bug`. (#62480)
self.ir
.tcx
.sess
.struct_span_err(expr.span, "`break` to unknown label")
.emit();
rustc_errors::FatalError.raise()
}
None => span_bug!(expr.span, "`break` to unknown label"),
}
}

View File

@ -33,6 +33,10 @@ type Res = def::Res<ast::NodeId>;
/// A vector of spans and replacements, a message and applicability.
crate type Suggestion = (Vec<(Span, String)>, String, Applicability);
/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a
/// similarly named label and whether or not it is reachable.
crate type LabelSuggestion = (Ident, bool);
crate struct TypoSuggestion {
pub candidate: Symbol,
pub res: Res,
@ -282,7 +286,7 @@ impl<'a> Resolver<'a> {
err.span_label(span, "used in a pattern more than once");
err
}
ResolutionError::UndeclaredLabel(name, lev_candidate) => {
ResolutionError::UndeclaredLabel { name, suggestion } => {
let mut err = struct_span_err!(
self.session,
span,
@ -290,16 +294,31 @@ impl<'a> Resolver<'a> {
"use of undeclared label `{}`",
name
);
if let Some(lev_candidate) = lev_candidate {
err.span_suggestion(
span,
"a label with a similar name exists in this scope",
lev_candidate.to_string(),
Applicability::MaybeIncorrect,
);
} else {
err.span_label(span, format!("undeclared label `{}`", name));
err.span_label(span, format!("undeclared label `{}`", name));
match suggestion {
// A reachable label with a similar name exists.
Some((ident, true)) => {
err.span_label(ident.span, "a label with a similar name is reachable");
err.span_suggestion(
span,
"try using similarly named label",
ident.name.to_string(),
Applicability::MaybeIncorrect,
);
}
// An unreachable label with a similar name exists.
Some((ident, false)) => {
err.span_label(
ident.span,
"a label with a similar name exists but is unreachable",
);
}
// No similarly-named labels exist.
None => (),
}
err
}
ResolutionError::SelfImportsOnlyAllowedWithin { root, span_with_rename } => {
@ -433,6 +452,45 @@ impl<'a> Resolver<'a> {
err.span_label(span, "`Self` in type parameter default".to_string());
err
}
ResolutionError::UnreachableLabel { name, definition_span, suggestion } => {
let mut err = struct_span_err!(
self.session,
span,
E0767,
"use of unreachable label `{}`",
name,
);
err.span_label(definition_span, "unreachable label defined here");
err.span_label(span, format!("unreachable label `{}`", name));
err.note(
"labels are unreachable through functions, closures, async blocks and modules",
);
match suggestion {
// A reachable label with a similar name exists.
Some((ident, true)) => {
err.span_label(ident.span, "a label with a similar name is reachable");
err.span_suggestion(
span,
"try using similarly named label",
ident.name.to_string(),
Applicability::MaybeIncorrect,
);
}
// An unreachable label with a similar name exists.
Some((ident, false)) => {
err.span_label(
ident.span,
"a label with a similar name exists but is also unreachable",
);
}
// No similarly-named labels exist.
None => (),
}
err
}
}
}

View File

@ -13,7 +13,6 @@ use crate::{ResolutionError, Resolver, Segment, UseError};
use rustc_ast::ast::*;
use rustc_ast::ptr::P;
use rustc_ast::util::lev_distance::find_best_match_for_name;
use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
use rustc_ast::{unwrap_or, walk_list};
use rustc_ast_lowering::ResolverAstLowering;
@ -101,6 +100,9 @@ crate enum RibKind<'a> {
/// upvars).
AssocItemRibKind,
/// We passed through a closure. Disallow labels.
ClosureOrAsyncRibKind,
/// We passed through a function definition. Disallow upvars.
/// Permit only those const parameters that are specified in the function's generics.
FnItemRibKind,
@ -124,11 +126,15 @@ crate enum RibKind<'a> {
}
impl RibKind<'_> {
// Whether this rib kind contains generic parameters, as opposed to local
// variables.
/// Whether this rib kind contains generic parameters, as opposed to local
/// variables.
crate fn contains_params(&self) -> bool {
match self {
NormalRibKind | FnItemRibKind | ConstantItemRibKind | ModuleRibKind(_)
NormalRibKind
| ClosureOrAsyncRibKind
| FnItemRibKind
| ConstantItemRibKind
| ModuleRibKind(_)
| MacroDefinition(_) => false,
AssocItemRibKind | ItemRibKind(_) | ForwardTyParamBanRibKind => true,
}
@ -474,7 +480,8 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
// Bail if there's no body.
FnKind::Fn(.., None) => return visit::walk_fn(self, fn_kind, sp),
FnKind::Fn(FnCtxt::Free | FnCtxt::Foreign, ..) => FnItemRibKind,
FnKind::Fn(FnCtxt::Assoc(_), ..) | FnKind::Closure(..) => NormalRibKind,
FnKind::Fn(FnCtxt::Assoc(_), ..) => NormalRibKind,
FnKind::Closure(..) => ClosureOrAsyncRibKind,
};
let previous_value =
replace(&mut self.diagnostic_metadata.current_function, Some((fn_kind, sp)));
@ -725,35 +732,79 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
}
}
/// Searches the current set of local scopes for labels. Returns the first non-`None` label that
/// is returned by the given predicate function
///
/// Stops after meeting a closure.
fn search_label<P, R>(&self, mut ident: Ident, pred: P) -> Option<R>
where
P: Fn(&Rib<'_, NodeId>, Ident) -> Option<R>,
{
for rib in self.label_ribs.iter().rev() {
match rib.kind {
NormalRibKind => {}
/// Searches the current set of local scopes for labels. Returns the `NodeId` of the resolved
/// label and reports an error if the label is not found or is unreachable.
fn resolve_label(&self, mut label: Ident) -> Option<NodeId> {
let mut suggestion = None;
// Preserve the original span so that errors contain "in this macro invocation"
// information.
let original_span = label.span;
for i in (0..self.label_ribs.len()).rev() {
let rib = &self.label_ribs[i];
if let MacroDefinition(def) = rib.kind {
// If an invocation of this macro created `ident`, give up on `ident`
// and switch to `ident`'s source from the macro definition.
MacroDefinition(def) => {
if def == self.r.macro_def(ident.span.ctxt()) {
ident.span.remove_mark();
}
}
_ => {
// Do not resolve labels across function boundary
return None;
if def == self.r.macro_def(label.span.ctxt()) {
label.span.remove_mark();
}
}
let r = pred(rib, ident);
if r.is_some() {
return r;
let ident = label.normalize_to_macro_rules();
if let Some((ident, id)) = rib.bindings.get_key_value(&ident) {
return if self.is_label_valid_from_rib(i) {
Some(*id)
} else {
self.r.report_error(
original_span,
ResolutionError::UnreachableLabel {
name: &label.name.as_str(),
definition_span: ident.span,
suggestion,
},
);
None
};
}
// Diagnostics: Check if this rib contains a label with a similar name, keep track of
// the first such label that is encountered.
suggestion = suggestion.or_else(|| self.suggestion_for_label_in_rib(i, label));
}
self.r.report_error(
original_span,
ResolutionError::UndeclaredLabel { name: &label.name.as_str(), suggestion },
);
None
}
/// Determine whether or not a label from the `rib_index`th label rib is reachable.
fn is_label_valid_from_rib(&self, rib_index: usize) -> bool {
let ribs = &self.label_ribs[rib_index + 1..];
for rib in ribs {
match rib.kind {
NormalRibKind | MacroDefinition(..) => {
// Nothing to do. Continue.
}
AssocItemRibKind
| ClosureOrAsyncRibKind
| FnItemRibKind
| ItemRibKind(..)
| ConstantItemRibKind
| ModuleRibKind(..)
| ForwardTyParamBanRibKind => {
return false;
}
}
}
None
true
}
fn resolve_adt(&mut self, item: &'ast Item, generics: &'ast Generics) {
@ -2044,35 +2095,10 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
}
ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => {
let node_id = self.search_label(label.ident, |rib, ident| {
rib.bindings.get(&ident.normalize_to_macro_rules()).cloned()
});
match node_id {
None => {
// Search again for close matches...
// Picks the first label that is "close enough", which is not necessarily
// the closest match
let close_match = self.search_label(label.ident, |rib, ident| {
let names = rib.bindings.iter().filter_map(|(id, _)| {
if id.span.ctxt() == label.ident.span.ctxt() {
Some(&id.name)
} else {
None
}
});
find_best_match_for_name(names, &ident.as_str(), None)
});
self.r.record_partial_res(expr.id, PartialRes::new(Res::Err));
self.r.report_error(
label.ident.span,
ResolutionError::UndeclaredLabel(&label.ident.as_str(), close_match),
);
}
Some(node_id) => {
// Since this res is a label, it is never read.
self.r.label_res_map.insert(expr.id, node_id);
self.diagnostic_metadata.unused_labels.remove(&node_id);
}
if let Some(node_id) = self.resolve_label(label.ident) {
// Since this res is a label, it is never read.
self.r.label_res_map.insert(expr.id, node_id);
self.diagnostic_metadata.unused_labels.remove(&node_id);
}
// visit `break` argument if any
@ -2144,21 +2170,26 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
// closure are detected as upvars rather than normal closure arg usages.
ExprKind::Closure(_, Async::Yes { .. }, _, ref fn_decl, ref body, _span) => {
self.with_rib(ValueNS, NormalRibKind, |this| {
// Resolve arguments:
this.resolve_params(&fn_decl.inputs);
// No need to resolve return type --
// the outer closure return type is `FnRetTy::Default`.
this.with_label_rib(ClosureOrAsyncRibKind, |this| {
// Resolve arguments:
this.resolve_params(&fn_decl.inputs);
// No need to resolve return type --
// the outer closure return type is `FnRetTy::Default`.
// Now resolve the inner closure
{
// No need to resolve arguments: the inner closure has none.
// Resolve the return type:
visit::walk_fn_ret_ty(this, &fn_decl.output);
// Resolve the body
this.visit_expr(body);
}
// Now resolve the inner closure
{
// No need to resolve arguments: the inner closure has none.
// Resolve the return type:
visit::walk_fn_ret_ty(this, &fn_decl.output);
// Resolve the body
this.visit_expr(body);
}
})
});
}
ExprKind::Async(..) | ExprKind::Closure(..) => {
self.with_label_rib(ClosureOrAsyncRibKind, |this| visit::walk_expr(this, expr));
}
_ => {
visit::walk_expr(self, expr);
}

View File

@ -1,4 +1,4 @@
use crate::diagnostics::{ImportSuggestion, TypoSuggestion};
use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion};
use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext};
use crate::late::{LateResolutionVisitor, RibKind};
use crate::path_names_to_string;
@ -992,6 +992,32 @@ impl<'a> LateResolutionVisitor<'a, '_, '_> {
}
None
}
/// Given the target `label`, search the `rib_index`th label rib for similarly named labels,
/// optionally returning the closest match and whether it is reachable.
crate fn suggestion_for_label_in_rib(
&self,
rib_index: usize,
label: Ident,
) -> Option<LabelSuggestion> {
// Are ribs from this `rib_index` within scope?
let within_scope = self.is_label_valid_from_rib(rib_index);
let rib = &self.label_ribs[rib_index];
let names = rib
.bindings
.iter()
.filter(|(id, _)| id.span.ctxt() == label.span.ctxt())
.map(|(id, _)| &id.name);
find_best_match_for_name(names, &label.as_str(), None).map(|symbol| {
// Upon finding a similar name, get the ident that it was from - the span
// contained within helps make a useful diagnostic. In addition, determine
// whether this candidate is within scope.
let (ident, _) = rib.bindings.iter().find(|(ident, _)| ident.name == symbol).unwrap();
(*ident, within_scope)
})
}
}
impl<'tcx> LifetimeContext<'_, 'tcx> {

View File

@ -61,7 +61,7 @@ use std::collections::BTreeSet;
use std::{cmp, fmt, iter, ptr};
use diagnostics::{extend_span_to_previous_binding, find_span_of_binding_until_next_binding};
use diagnostics::{ImportSuggestion, Suggestion};
use diagnostics::{ImportSuggestion, LabelSuggestion, Suggestion};
use imports::{Import, ImportKind, ImportResolver, NameResolution};
use late::{HasGenericParams, PathSource, Rib, RibKind::*};
use macros::{MacroRulesBinding, MacroRulesScope};
@ -197,7 +197,7 @@ enum ResolutionError<'a> {
/// Error E0416: identifier is bound more than once in the same pattern.
IdentifierBoundMoreThanOnceInSamePattern(&'a str),
/// Error E0426: use of undeclared label.
UndeclaredLabel(&'a str, Option<Symbol>),
UndeclaredLabel { name: &'a str, suggestion: Option<LabelSuggestion> },
/// Error E0429: `self` imports are only allowed within a `{ }` list.
SelfImportsOnlyAllowedWithin { root: bool, span_with_rename: Span },
/// Error E0430: `self` import can only appear once in the list.
@ -216,6 +216,8 @@ enum ResolutionError<'a> {
ForwardDeclaredTyParam, // FIXME(const_generics:defaults)
/// Error E0735: type parameters with a default cannot use `Self`
SelfInTyParamDefault,
/// Error E0767: use of unreachable label
UnreachableLabel { name: &'a str, definition_span: Span, suggestion: Option<LabelSuggestion> },
}
enum VisResolutionError<'a> {
@ -2453,6 +2455,7 @@ impl<'a> Resolver<'a> {
for rib in ribs {
match rib.kind {
NormalRibKind
| ClosureOrAsyncRibKind
| ModuleRibKind(..)
| MacroDefinition(..)
| ForwardTyParamBanRibKind => {
@ -2488,6 +2491,7 @@ impl<'a> Resolver<'a> {
for rib in ribs {
let has_generic_params = match rib.kind {
NormalRibKind
| ClosureOrAsyncRibKind
| AssocItemRibKind
| ModuleRibKind(..)
| MacroDefinition(..)

View File

@ -27,7 +27,9 @@ fn main() {
// not the `loop`, which failed in the call to `find_breakable`. (#65383)
'lab: loop {
|| {
break 'lab; //~ ERROR `break` inside of a closure
break 'lab;
//~^ ERROR use of unreachable label `'lab`
//~| ERROR `break` inside of a closure
};
}
}

View File

@ -1,3 +1,14 @@
error[E0767]: use of unreachable label `'lab`
--> $DIR/break-outside-loop.rs:30:19
|
LL | 'lab: loop {
| ---- unreachable label defined here
LL | || {
LL | break 'lab;
| ^^^^ unreachable label `'lab`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error[E0268]: `break` outside of a loop
--> $DIR/break-outside-loop.rs:10:15
|
@ -41,7 +52,7 @@ LL | || {
LL | break 'lab;
| ^^^^^^^^^^ cannot `break` inside of a closure
error: aborting due to 6 previous errors
error: aborting due to 7 previous errors
Some errors have detailed explanations: E0267, E0268.
Some errors have detailed explanations: E0267, E0268, E0767.
For more information about an error, try `rustc --explain E0267`.

View File

@ -0,0 +1,7 @@
fn main () {
'a: loop {
|| {
loop { break 'a; } //~ ERROR E0767
}
}
}

View File

@ -0,0 +1,14 @@
error[E0767]: use of unreachable label `'a`
--> $DIR/E0767.rs:4:26
|
LL | 'a: loop {
| -- unreachable label defined here
LL | || {
LL | loop { break 'a; }
| ^^ unreachable label `'a`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error: aborting due to previous error
For more information about this error, try `rustc --explain E0767`.

View File

@ -5,6 +5,8 @@ fn main() {
// `propagate_through_expr` would be the closure and not the `loop`, which wouldn't be found in
// `self.break_ln`. (#62480)
'a: {
|| break 'a //~ ERROR `break` to unknown label
|| break 'a
//~^ ERROR use of unreachable label `'a`
//~| ERROR `break` inside of a closure
}
}

View File

@ -1,8 +1,22 @@
error: `break` to unknown label
error[E0767]: use of unreachable label `'a`
--> $DIR/issue-62480.rs:8:18
|
LL | 'a: {
| -- unreachable label defined here
LL | || break 'a
| ^^ unreachable label `'a`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error[E0267]: `break` inside of a closure
--> $DIR/issue-62480.rs:8:12
|
LL | || break 'a
| ^^^^^^^^
| -- ^^^^^^^^ cannot `break` inside of a closure
| |
| enclosing closure
error: aborting due to previous error
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0267, E0767.
For more information about an error, try `rustc --explain E0267`.

View File

@ -2,6 +2,8 @@
fn main() {
'some_label: loop {
|| break 'some_label (); //~ ERROR: `break` inside of a closure
|| break 'some_label ();
//~^ ERROR: use of unreachable label `'some_label`
//~| ERROR: `break` inside of a closure
}
}

View File

@ -1,3 +1,13 @@
error[E0767]: use of unreachable label `'some_label`
--> $DIR/issue-66702-break-outside-loop-val.rs:5:18
|
LL | 'some_label: loop {
| ----------- unreachable label defined here
LL | || break 'some_label ();
| ^^^^^^^^^^^ unreachable label `'some_label`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error[E0267]: `break` inside of a closure
--> $DIR/issue-66702-break-outside-loop-val.rs:5:12
|
@ -6,6 +16,7 @@ LL | || break 'some_label ();
| |
| enclosing closure
error: aborting due to previous error
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0267`.
Some errors have detailed explanations: E0267, E0767.
For more information about an error, try `rustc --explain E0267`.

View File

@ -0,0 +1,12 @@
// edition:2018
fn main() {
'a: loop {
async {
loop {
continue 'a
//~^ ERROR use of unreachable label `'a`
}
};
}
}

View File

@ -0,0 +1,14 @@
error[E0767]: use of unreachable label `'a`
--> $DIR/issue-73541-1.rs:7:26
|
LL | 'a: loop {
| -- unreachable label defined here
...
LL | continue 'a
| ^^ unreachable label `'a`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error: aborting due to previous error
For more information about this error, try `rustc --explain E0767`.

View File

@ -0,0 +1,20 @@
// edition:2018
async fn c() {
'a: loop {
macro_rules! b {
() => {
continue 'a
//~^ ERROR use of unreachable label `'a`
}
}
async {
loop {
b!();
}
};
}
}
fn main() { }

View File

@ -0,0 +1,18 @@
error[E0767]: use of unreachable label `'a`
--> $DIR/issue-73541-2.rs:7:26
|
LL | 'a: loop {
| -- unreachable label defined here
...
LL | continue 'a
| ^^ unreachable label `'a`
...
LL | b!();
| ----- in this macro invocation
|
= note: labels are unreachable through functions, closures, async blocks and modules
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0767`.

View File

@ -0,0 +1,9 @@
fn main() {
'aaaaab: loop {
|| {
loop { continue 'aaaaaa }
//~^ ERROR use of undeclared label `'aaaaaa`
};
}
}

View File

@ -0,0 +1,12 @@
error[E0426]: use of undeclared label `'aaaaaa`
--> $DIR/issue-73541-3.rs:4:29
|
LL | 'aaaaab: loop {
| ------- a label with a similar name exists but is unreachable
LL | || {
LL | loop { continue 'aaaaaa }
| ^^^^^^^ undeclared label `'aaaaaa`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0426`.

View File

@ -0,0 +1,9 @@
fn main() {
'a: loop {
|| {
loop { continue 'a }
//~^ ERROR use of unreachable label `'a`
};
}
}

View File

@ -0,0 +1,14 @@
error[E0767]: use of unreachable label `'a`
--> $DIR/issue-73541.rs:4:29
|
LL | 'a: loop {
| -- unreachable label defined here
LL | || {
LL | loop { continue 'a }
| ^^ unreachable label `'a`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error: aborting due to previous error
For more information about this error, try `rustc --explain E0767`.

View File

@ -2,7 +2,7 @@ fn f() {
'l: loop {
fn g() {
loop {
break 'l; //~ ERROR use of undeclared label
break 'l; //~ ERROR use of unreachable label
}
}
}

View File

@ -1,9 +1,14 @@
error[E0426]: use of undeclared label `'l`
error[E0767]: use of unreachable label `'l`
--> $DIR/resolve-label.rs:5:23
|
LL | 'l: loop {
| -- unreachable label defined here
...
LL | break 'l;
| ^^ undeclared label `'l`
| ^^ unreachable label `'l`
|
= note: labels are unreachable through functions, closures, async blocks and modules
error: aborting due to previous error
For more information about this error, try `rustc --explain E0426`.
For more information about this error, try `rustc --explain E0767`.

View File

@ -1,35 +1,35 @@
error[E0426]: use of undeclared label `'fo`
--> $DIR/suggest-labels.rs:4:15
|
LL | 'foo: loop {
| ---- a label with a similar name is reachable
LL | break 'fo;
| ^^^
|
help: a label with a similar name exists in this scope
|
LL | break 'foo;
| ^^^^
| |
| undeclared label `'fo`
| help: try using similarly named label: `'foo`
error[E0426]: use of undeclared label `'bor`
--> $DIR/suggest-labels.rs:8:18
|
LL | 'bar: loop {
| ---- a label with a similar name is reachable
LL | continue 'bor;
| ^^^^
|
help: a label with a similar name exists in this scope
|
LL | continue 'bar;
| ^^^^
| |
| undeclared label `'bor`
| help: try using similarly named label: `'bar`
error[E0426]: use of undeclared label `'longlable`
--> $DIR/suggest-labels.rs:13:19
|
LL | 'longlabel1: loop {
| ----------- a label with a similar name is reachable
LL | break 'longlable;
| ^^^^^^^^^^
|
help: a label with a similar name exists in this scope
|
LL | break 'longlabel1;
| ^^^^^^^^^^^
| |
| undeclared label `'longlable`
| help: try using similarly named label: `'longlabel1`
error: aborting due to 3 previous errors