Add new pattern_complexity
attribute to add possibility to limit and check recursion in pattern matching
This commit is contained in:
parent
5257aee7dd
commit
be31b6b6cd
@ -929,6 +929,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing,
|
omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing,
|
||||||
"the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite",
|
"the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite",
|
||||||
),
|
),
|
||||||
|
rustc_attr!(
|
||||||
|
TEST, pattern_complexity, CrateLevel, template!(NameValueStr: "N"),
|
||||||
|
ErrorFollowing, @only_local: true,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn deprecated_attributes() -> Vec<&'static BuiltinAttribute> {
|
pub fn deprecated_attributes() -> Vec<&'static BuiltinAttribute> {
|
||||||
|
@ -213,6 +213,8 @@ declare_features! (
|
|||||||
(internal, negative_bounds, "1.71.0", None),
|
(internal, negative_bounds, "1.71.0", None),
|
||||||
/// Allows using `#[omit_gdb_pretty_printer_section]`.
|
/// Allows using `#[omit_gdb_pretty_printer_section]`.
|
||||||
(internal, omit_gdb_pretty_printer_section, "1.5.0", None),
|
(internal, omit_gdb_pretty_printer_section, "1.5.0", None),
|
||||||
|
/// Set the maximum pattern complexity allowed (not limited by default).
|
||||||
|
(internal, pattern_complexity, "CURRENT_RUSTC_VERSION", None),
|
||||||
/// Allows using `#[prelude_import]` on glob `use` items.
|
/// Allows using `#[prelude_import]` on glob `use` items.
|
||||||
(internal, prelude_import, "1.2.0", None),
|
(internal, prelude_import, "1.2.0", None),
|
||||||
/// Used to identify crates that contain the profiler runtime.
|
/// Used to identify crates that contain the profiler runtime.
|
||||||
|
@ -40,6 +40,13 @@ pub fn get_recursion_limit(krate_attrs: &[Attribute], sess: &Session) -> Limit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: usize) -> Limit {
|
fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: usize) -> Limit {
|
||||||
|
match get_limit_size(krate_attrs, sess, name) {
|
||||||
|
Some(size) => Limit::new(size),
|
||||||
|
None => Limit::new(default),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_limit_size(krate_attrs: &[Attribute], sess: &Session, name: Symbol) -> Option<usize> {
|
||||||
for attr in krate_attrs {
|
for attr in krate_attrs {
|
||||||
if !attr.has_name(name) {
|
if !attr.has_name(name) {
|
||||||
continue;
|
continue;
|
||||||
@ -47,7 +54,7 @@ fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: u
|
|||||||
|
|
||||||
if let Some(s) = attr.value_str() {
|
if let Some(s) = attr.value_str() {
|
||||||
match s.as_str().parse() {
|
match s.as_str().parse() {
|
||||||
Ok(n) => return Limit::new(n),
|
Ok(n) => return Some(n),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let value_span = attr
|
let value_span = attr
|
||||||
.meta()
|
.meta()
|
||||||
@ -69,5 +76,5 @@ fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Limit::new(default);
|
None
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ use rustc_hir as hir;
|
|||||||
use rustc_hir::def::*;
|
use rustc_hir::def::*;
|
||||||
use rustc_hir::def_id::LocalDefId;
|
use rustc_hir::def_id::LocalDefId;
|
||||||
use rustc_hir::HirId;
|
use rustc_hir::HirId;
|
||||||
|
use rustc_middle::middle::limits::get_limit_size;
|
||||||
use rustc_middle::thir::visit::Visitor;
|
use rustc_middle::thir::visit::Visitor;
|
||||||
use rustc_middle::thir::*;
|
use rustc_middle::thir::*;
|
||||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||||
@ -26,7 +27,7 @@ use rustc_session::lint::builtin::{
|
|||||||
};
|
};
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
use rustc_span::hygiene::DesugaringKind;
|
use rustc_span::hygiene::DesugaringKind;
|
||||||
use rustc_span::Span;
|
use rustc_span::{sym, Span};
|
||||||
|
|
||||||
pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
|
pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
|
||||||
let typeck_results = tcx.typeck(def_id);
|
let typeck_results = tcx.typeck(def_id);
|
||||||
@ -403,8 +404,11 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
|
|||||||
arms: &[MatchArm<'p, 'tcx>],
|
arms: &[MatchArm<'p, 'tcx>],
|
||||||
scrut_ty: Ty<'tcx>,
|
scrut_ty: Ty<'tcx>,
|
||||||
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
|
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
|
||||||
|
let pattern_complexity_limit =
|
||||||
|
get_limit_size(cx.tcx.hir().krate_attrs(), cx.tcx.sess, sym::pattern_complexity);
|
||||||
let report =
|
let report =
|
||||||
rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty).map_err(|err| {
|
rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty, pattern_complexity_limit)
|
||||||
|
.map_err(|err| {
|
||||||
self.error = Err(err);
|
self.error = Err(err);
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
@ -142,6 +142,9 @@ pub trait TypeCx: Sized + fmt::Debug {
|
|||||||
_overlaps_with: &[&DeconstructedPat<Self>],
|
_overlaps_with: &[&DeconstructedPat<Self>],
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The maximum pattern complexity limit was reached.
|
||||||
|
fn complexity_exceeded(&self) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The arm of a match expression.
|
/// The arm of a match expression.
|
||||||
@ -167,10 +170,12 @@ pub fn analyze_match<'p, 'tcx>(
|
|||||||
tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
|
tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
|
||||||
arms: &[rustc::MatchArm<'p, 'tcx>],
|
arms: &[rustc::MatchArm<'p, 'tcx>],
|
||||||
scrut_ty: Ty<'tcx>,
|
scrut_ty: Ty<'tcx>,
|
||||||
|
pattern_complexity_limit: Option<usize>,
|
||||||
) -> Result<rustc::UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
|
) -> Result<rustc::UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
|
||||||
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
|
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
|
||||||
let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
|
let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
|
||||||
let report = compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity)?;
|
let report =
|
||||||
|
compute_match_usefulness(tycx, arms, scrut_ty, scrut_validity, pattern_complexity_limit)?;
|
||||||
|
|
||||||
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
|
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
|
||||||
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
|
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
|
||||||
|
@ -895,6 +895,11 @@ impl<'p, 'tcx: 'p> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
|
|||||||
errors::OverlappingRangeEndpoints { overlap: overlaps, range: pat_span },
|
errors::OverlappingRangeEndpoints { overlap: overlaps, range: pat_span },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn complexity_exceeded(&self) -> Result<(), Self::Error> {
|
||||||
|
let span = self.whole_match_span.unwrap_or(self.scrut_span);
|
||||||
|
Err(self.tcx.dcx().span_err(span, "reached pattern complexity limit"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.
|
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.
|
||||||
|
@ -734,6 +734,21 @@ struct UsefulnessCtxt<'a, Cx: TypeCx> {
|
|||||||
/// Collect the patterns found useful during usefulness checking. This is used to lint
|
/// Collect the patterns found useful during usefulness checking. This is used to lint
|
||||||
/// unreachable (sub)patterns.
|
/// unreachable (sub)patterns.
|
||||||
useful_subpatterns: FxHashSet<PatId>,
|
useful_subpatterns: FxHashSet<PatId>,
|
||||||
|
complexity_limit: Option<usize>,
|
||||||
|
complexity_level: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Cx: TypeCx> UsefulnessCtxt<'a, Cx> {
|
||||||
|
fn increase_complexity_level(&mut self, complexity_add: usize) -> Result<(), Cx::Error> {
|
||||||
|
self.complexity_level += complexity_add;
|
||||||
|
if self
|
||||||
|
.complexity_limit
|
||||||
|
.is_some_and(|complexity_limit| complexity_limit < self.complexity_level)
|
||||||
|
{
|
||||||
|
return self.tycx.complexity_exceeded();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context that provides information local to a place under investigation.
|
/// Context that provides information local to a place under investigation.
|
||||||
@ -1552,6 +1567,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(place) = matrix.head_place() else {
|
let Some(place) = matrix.head_place() else {
|
||||||
|
mcx.increase_complexity_level(matrix.rows().len())?;
|
||||||
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
|
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
|
||||||
// A row is useful iff it has no (unguarded) rows above it.
|
// A row is useful iff it has no (unguarded) rows above it.
|
||||||
let mut useful = true; // Whether the next row is useful.
|
let mut useful = true; // Whether the next row is useful.
|
||||||
@ -1690,8 +1706,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
|
|||||||
arms: &[MatchArm<'p, Cx>],
|
arms: &[MatchArm<'p, Cx>],
|
||||||
scrut_ty: Cx::Ty,
|
scrut_ty: Cx::Ty,
|
||||||
scrut_validity: ValidityConstraint,
|
scrut_validity: ValidityConstraint,
|
||||||
|
complexity_limit: Option<usize>,
|
||||||
) -> Result<UsefulnessReport<'p, Cx>, Cx::Error> {
|
) -> Result<UsefulnessReport<'p, Cx>, Cx::Error> {
|
||||||
let mut cx = UsefulnessCtxt { tycx, useful_subpatterns: FxHashSet::default() };
|
let mut cx = UsefulnessCtxt {
|
||||||
|
tycx,
|
||||||
|
useful_subpatterns: FxHashSet::default(),
|
||||||
|
complexity_limit,
|
||||||
|
complexity_level: 0,
|
||||||
|
};
|
||||||
let mut matrix = Matrix::new(arms, scrut_ty, scrut_validity);
|
let mut matrix = Matrix::new(arms, scrut_ty, scrut_validity);
|
||||||
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(&mut cx, &mut matrix)?;
|
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(&mut cx, &mut matrix)?;
|
||||||
|
|
||||||
|
@ -1273,6 +1273,7 @@ symbols! {
|
|||||||
pat,
|
pat,
|
||||||
pat_param,
|
pat_param,
|
||||||
path,
|
path,
|
||||||
|
pattern_complexity,
|
||||||
pattern_parentheses,
|
pattern_parentheses,
|
||||||
phantom_data,
|
phantom_data,
|
||||||
pic,
|
pic,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user