Skip to content

Commit 71704c4

Browse files
committed
Auto merge of #116623 - Nadrieril:validate-range-endpoints, r=oli-obk
Fix overflow checking in range patterns When a range pattern contains an overflowing literal, if we're not careful we might not notice the overflow and use the wrapped value. This makes for confusing error messages because linting against overflowing literals is only done in a later pass. So when a range is invalid we check for overflows to provide a better error. This check didn't use to handle negative types; this PR fixes that. First commit adds tests, second cleans up without changing behavior, third does the fix. EDIT: while I was at it, I fixed a small annoyance about the span of the overflow lint on negated literals. Fixes #94239
2 parents 156da98 + dcdddb7 commit 71704c4

11 files changed

+318
-168
lines changed

compiler/rustc_lint/src/types.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,15 @@ declare_lint! {
140140
pub struct TypeLimits {
141141
/// Id of the last visited negated expression
142142
negated_expr_id: Option<hir::HirId>,
143+
/// Span of the last visited negated expression
144+
negated_expr_span: Option<Span>,
143145
}
144146

145147
impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS, INVALID_NAN_COMPARISONS]);
146148

147149
impl TypeLimits {
148150
pub fn new() -> TypeLimits {
149-
TypeLimits { negated_expr_id: None }
151+
TypeLimits { negated_expr_id: None, negated_expr_span: None }
150152
}
151153
}
152154

@@ -426,17 +428,15 @@ fn lint_int_literal<'tcx>(
426428
return;
427429
}
428430

429-
let lit = cx
430-
.sess()
431-
.source_map()
432-
.span_to_snippet(lit.span)
433-
.expect("must get snippet from literal");
431+
let span = if negative { type_limits.negated_expr_span.unwrap() } else { e.span };
432+
let lit =
433+
cx.sess().source_map().span_to_snippet(span).expect("must get snippet from literal");
434434
let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
435435
.map(|suggestion_ty| OverflowingIntHelp { suggestion_ty });
436436

437437
cx.emit_spanned_lint(
438438
OVERFLOWING_LITERALS,
439-
e.span,
439+
span,
440440
OverflowingInt { ty: t.name_str(), lit, min, max, help },
441441
);
442442
}
@@ -622,9 +622,10 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
622622
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
623623
match e.kind {
624624
hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => {
625-
// propagate negation, if the negation itself isn't negated
625+
// Propagate negation, if the negation itself isn't negated
626626
if self.negated_expr_id != Some(e.hir_id) {
627627
self.negated_expr_id = Some(expr.hir_id);
628+
self.negated_expr_span = Some(e.span);
628629
}
629630
}
630631
hir::ExprKind::Binary(binop, ref l, ref r) => {

compiler/rustc_mir_build/messages.ftl

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ mir_build_leading_irrefutable_let_patterns = leading irrefutable {$count ->
173173
174174
mir_build_literal_in_range_out_of_bounds =
175175
literal out of range for `{$ty}`
176-
.label = this value doesn't fit in `{$ty}` whose maximum value is `{$max}`
176+
.label = this value does not fit into the type `{$ty}` whose range is `{$min}..={$max}`
177177
178178
mir_build_lower_range_bound_must_be_less_than_or_equal_to_upper =
179179
lower range bound must be less than or equal to upper

compiler/rustc_mir_build/src/errors.rs

+1
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ pub struct LiteralOutOfRange<'tcx> {
551551
#[label]
552552
pub span: Span,
553553
pub ty: Ty<'tcx>,
554+
pub min: i128,
554555
pub max: u128,
555556
}
556557

compiler/rustc_mir_build/src/thir/pattern/mod.rs

+136-131
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ use rustc_index::Idx;
2020
use rustc_middle::mir::interpret::{
2121
ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar,
2222
};
23-
use rustc_middle::mir::{self, Const, UserTypeProjection};
24-
use rustc_middle::mir::{BorrowKind, Mutability};
23+
use rustc_middle::mir::{self, BorrowKind, Const, Mutability, UserTypeProjection};
2524
use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange};
26-
use rustc_middle::ty::CanonicalUserTypeAnnotation;
27-
use rustc_middle::ty::TypeVisitableExt;
28-
use rustc_middle::ty::{self, AdtDef, Region, Ty, TyCtxt, UserType};
29-
use rustc_middle::ty::{GenericArg, GenericArgsRef};
30-
use rustc_span::{Span, Symbol};
31-
use rustc_target::abi::FieldIdx;
25+
use rustc_middle::ty::layout::IntegerExt;
26+
use rustc_middle::ty::{
27+
self, AdtDef, CanonicalUserTypeAnnotation, GenericArg, GenericArgsRef, Region, Ty, TyCtxt,
28+
TypeVisitableExt, UserType,
29+
};
30+
use rustc_span::{ErrorGuaranteed, Span, Symbol};
31+
use rustc_target::abi::{FieldIdx, Integer};
3232

3333
use std::cmp::Ordering;
3434

@@ -85,127 +85,159 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
8585
)
8686
}
8787

88-
fn lower_range_expr(
88+
fn lower_pattern_range_endpoint(
8989
&mut self,
90-
expr: &'tcx hir::Expr<'tcx>,
91-
) -> (PatKind<'tcx>, Option<Ascription<'tcx>>) {
92-
match self.lower_lit(expr) {
93-
PatKind::AscribeUserType { ascription, subpattern: box Pat { kind, .. } } => {
94-
(kind, Some(ascription))
90+
expr: Option<&'tcx hir::Expr<'tcx>>,
91+
) -> Result<(Option<mir::Const<'tcx>>, Option<Ascription<'tcx>>), ErrorGuaranteed> {
92+
match expr {
93+
None => Ok((None, None)),
94+
Some(expr) => {
95+
let (kind, ascr) = match self.lower_lit(expr) {
96+
PatKind::AscribeUserType { ascription, subpattern: box Pat { kind, .. } } => {
97+
(kind, Some(ascription))
98+
}
99+
kind => (kind, None),
100+
};
101+
let value = if let PatKind::Constant { value } = kind {
102+
value
103+
} else {
104+
let msg = format!(
105+
"found bad range pattern endpoint `{expr:?}` outside of error recovery"
106+
);
107+
return Err(self.tcx.sess.delay_span_bug(expr.span, msg));
108+
};
109+
Ok((Some(value), ascr))
95110
}
96-
kind => (kind, None),
97111
}
98112
}
99113

114+
/// Overflowing literals are linted against in a late pass. This is mostly fine, except when we
115+
/// encounter a range pattern like `-130i8..2`: if we believe `eval_bits`, this looks like a
116+
/// range where the endpoints are in the wrong order. To avoid a confusing error message, we
117+
/// check for overflow then.
118+
/// This is only called when the range is already known to be malformed.
119+
fn error_on_literal_overflow(
120+
&self,
121+
expr: Option<&'tcx hir::Expr<'tcx>>,
122+
ty: Ty<'tcx>,
123+
) -> Result<(), ErrorGuaranteed> {
124+
use hir::{ExprKind, UnOp};
125+
use rustc_ast::ast::LitKind;
126+
127+
let Some(mut expr) = expr else {
128+
return Ok(());
129+
};
130+
let span = expr.span;
131+
132+
// We need to inspect the original expression, because if we only inspect the output of
133+
// `eval_bits`, an overflowed value has already been wrapped around.
134+
// We mostly copy the logic from the `rustc_lint::OVERFLOWING_LITERALS` lint.
135+
let mut negated = false;
136+
if let ExprKind::Unary(UnOp::Neg, sub_expr) = expr.kind {
137+
negated = true;
138+
expr = sub_expr;
139+
}
140+
let ExprKind::Lit(lit) = expr.kind else {
141+
return Ok(());
142+
};
143+
let LitKind::Int(lit_val, _) = lit.node else {
144+
return Ok(());
145+
};
146+
let (min, max): (i128, u128) = match ty.kind() {
147+
ty::Int(ity) => {
148+
let size = Integer::from_int_ty(&self.tcx, *ity).size();
149+
(size.signed_int_min(), size.signed_int_max() as u128)
150+
}
151+
ty::Uint(uty) => {
152+
let size = Integer::from_uint_ty(&self.tcx, *uty).size();
153+
(0, size.unsigned_int_max())
154+
}
155+
_ => {
156+
return Ok(());
157+
}
158+
};
159+
// Detect literal value out of range `[min, max]` inclusive, avoiding use of `-min` to
160+
// prevent overflow/panic.
161+
if (negated && lit_val > max + 1) || (!negated && lit_val > max) {
162+
return Err(self.tcx.sess.emit_err(LiteralOutOfRange { span, ty, min, max }));
163+
}
164+
Ok(())
165+
}
166+
100167
fn lower_pattern_range(
101168
&mut self,
102-
ty: Ty<'tcx>,
103-
lo: mir::Const<'tcx>,
104-
hi: mir::Const<'tcx>,
169+
lo_expr: Option<&'tcx hir::Expr<'tcx>>,
170+
hi_expr: Option<&'tcx hir::Expr<'tcx>>,
105171
end: RangeEnd,
172+
ty: Ty<'tcx>,
106173
span: Span,
107-
lo_expr: Option<&hir::Expr<'tcx>>,
108-
hi_expr: Option<&hir::Expr<'tcx>>,
109-
) -> PatKind<'tcx> {
174+
) -> Result<PatKind<'tcx>, ErrorGuaranteed> {
175+
if lo_expr.is_none() && hi_expr.is_none() {
176+
let msg = format!("found twice-open range pattern (`..`) outside of error recovery");
177+
return Err(self.tcx.sess.delay_span_bug(span, msg));
178+
}
179+
180+
let (lo, lo_ascr) = self.lower_pattern_range_endpoint(lo_expr)?;
181+
let (hi, hi_ascr) = self.lower_pattern_range_endpoint(hi_expr)?;
182+
183+
let lo = lo.unwrap_or_else(|| {
184+
// Unwrap is ok because the type is known to be numeric.
185+
let lo = ty.numeric_min_val(self.tcx).unwrap();
186+
mir::Const::from_ty_const(lo, self.tcx)
187+
});
188+
let hi = hi.unwrap_or_else(|| {
189+
// Unwrap is ok because the type is known to be numeric.
190+
let hi = ty.numeric_max_val(self.tcx).unwrap();
191+
mir::Const::from_ty_const(hi, self.tcx)
192+
});
110193
assert_eq!(lo.ty(), ty);
111194
assert_eq!(hi.ty(), ty);
195+
112196
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env);
113-
let max = || {
114-
self.tcx
115-
.layout_of(self.param_env.with_reveal_all_normalized(self.tcx).and(ty))
116-
.ok()
117-
.unwrap()
118-
.size
119-
.unsigned_int_max()
120-
};
121-
match (end, cmp) {
197+
let mut kind = match (end, cmp) {
122198
// `x..y` where `x < y`.
123199
// Non-empty because the range includes at least `x`.
124200
(RangeEnd::Excluded, Some(Ordering::Less)) => {
125201
PatKind::Range(Box::new(PatRange { lo, hi, end }))
126202
}
127-
// `x..y` where `x >= y`. The range is empty => error.
128-
(RangeEnd::Excluded, _) => {
129-
let mut lower_overflow = false;
130-
let mut higher_overflow = false;
131-
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = lo_expr
132-
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node
133-
{
134-
if lo.eval_bits(self.tcx, self.param_env) != val {
135-
lower_overflow = true;
136-
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
137-
}
138-
}
139-
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = hi_expr
140-
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node
141-
{
142-
if hi.eval_bits(self.tcx, self.param_env) != val {
143-
higher_overflow = true;
144-
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
145-
}
146-
}
147-
if !lower_overflow && !higher_overflow {
148-
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanUpper { span });
149-
}
150-
PatKind::Wild
151-
}
152203
// `x..=y` where `x == y`.
153204
(RangeEnd::Included, Some(Ordering::Equal)) => PatKind::Constant { value: lo },
154205
// `x..=y` where `x < y`.
155206
(RangeEnd::Included, Some(Ordering::Less)) => {
156207
PatKind::Range(Box::new(PatRange { lo, hi, end }))
157208
}
158-
// `x..=y` where `x > y` hence the range is empty => error.
159-
(RangeEnd::Included, _) => {
160-
let mut lower_overflow = false;
161-
let mut higher_overflow = false;
162-
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = lo_expr
163-
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node
164-
{
165-
if lo.eval_bits(self.tcx, self.param_env) != val {
166-
lower_overflow = true;
167-
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
209+
// `x..y` where `x >= y`, or `x..=y` where `x > y`. The range is empty => error.
210+
_ => {
211+
// Emit a more appropriate message if there was overflow.
212+
self.error_on_literal_overflow(lo_expr, ty)?;
213+
self.error_on_literal_overflow(hi_expr, ty)?;
214+
let e = match end {
215+
RangeEnd::Included => {
216+
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanOrEqualToUpper {
217+
span,
218+
teach: self.tcx.sess.teach(&error_code!(E0030)).then_some(()),
219+
})
168220
}
169-
}
170-
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = hi_expr
171-
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node
172-
{
173-
if hi.eval_bits(self.tcx, self.param_env) != val {
174-
higher_overflow = true;
175-
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
221+
RangeEnd::Excluded => {
222+
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanUpper { span })
176223
}
177-
}
178-
if !lower_overflow && !higher_overflow {
179-
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanOrEqualToUpper {
180-
span,
181-
teach: self.tcx.sess.teach(&error_code!(E0030)).then_some(()),
182-
});
183-
}
184-
PatKind::Wild
224+
};
225+
return Err(e);
185226
}
186-
}
187-
}
227+
};
188228

189-
fn normalize_range_pattern_ends(
190-
&self,
191-
ty: Ty<'tcx>,
192-
lo: Option<&PatKind<'tcx>>,
193-
hi: Option<&PatKind<'tcx>>,
194-
) -> Option<(mir::Const<'tcx>, mir::Const<'tcx>)> {
195-
match (lo, hi) {
196-
(Some(PatKind::Constant { value: lo }), Some(PatKind::Constant { value: hi })) => {
197-
Some((*lo, *hi))
198-
}
199-
(Some(PatKind::Constant { value: lo }), None) => {
200-
let hi = ty.numeric_max_val(self.tcx)?;
201-
Some((*lo, mir::Const::from_ty_const(hi, self.tcx)))
202-
}
203-
(None, Some(PatKind::Constant { value: hi })) => {
204-
let lo = ty.numeric_min_val(self.tcx)?;
205-
Some((mir::Const::from_ty_const(lo, self.tcx), *hi))
229+
// If we are handling a range with associated constants (e.g.
230+
// `Foo::<'a>::A..=Foo::B`), we need to put the ascriptions for the associated
231+
// constants somewhere. Have them on the range pattern.
232+
for ascr in [lo_ascr, hi_ascr] {
233+
if let Some(ascription) = ascr {
234+
kind = PatKind::AscribeUserType {
235+
ascription,
236+
subpattern: Box::new(Pat { span, ty, kind }),
237+
};
206238
}
207-
_ => None,
208239
}
240+
Ok(kind)
209241
}
210242

211243
#[instrument(skip(self), level = "debug")]
@@ -220,37 +252,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
220252

221253
hir::PatKind::Range(ref lo_expr, ref hi_expr, end) => {
222254
let (lo_expr, hi_expr) = (lo_expr.as_deref(), hi_expr.as_deref());
223-
let lo_span = lo_expr.map_or(pat.span, |e| e.span);
224-
let lo = lo_expr.map(|e| self.lower_range_expr(e));
225-
let hi = hi_expr.map(|e| self.lower_range_expr(e));
226-
227-
let (lp, hp) = (lo.as_ref().map(|(x, _)| x), hi.as_ref().map(|(x, _)| x));
228-
let mut kind = match self.normalize_range_pattern_ends(ty, lp, hp) {
229-
Some((lc, hc)) => {
230-
self.lower_pattern_range(ty, lc, hc, end, lo_span, lo_expr, hi_expr)
231-
}
232-
None => {
233-
let msg = format!(
234-
"found bad range pattern `{:?}` outside of error recovery",
235-
(&lo, &hi),
236-
);
237-
self.tcx.sess.delay_span_bug(pat.span, msg);
238-
PatKind::Wild
239-
}
240-
};
241-
242-
// If we are handling a range with associated constants (e.g.
243-
// `Foo::<'a>::A..=Foo::B`), we need to put the ascriptions for the associated
244-
// constants somewhere. Have them on the range pattern.
245-
for end in &[lo, hi] {
246-
if let Some((_, Some(ascription))) = end {
247-
let subpattern = Box::new(Pat { span: pat.span, ty, kind });
248-
kind =
249-
PatKind::AscribeUserType { ascription: ascription.clone(), subpattern };
250-
}
251-
}
252-
253-
kind
255+
// FIXME?: returning `_` can cause inaccurate "unreachable" warnings. This can be
256+
// fixed by returning `PatKind::Const(ConstKind::Error(...))` if #115937 gets
257+
// merged.
258+
self.lower_pattern_range(lo_expr, hi_expr, end, ty, span).unwrap_or(PatKind::Wild)
254259
}
255260

256261
hir::PatKind::Path(ref qpath) => {

tests/ui/error-codes/E0030-teach.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error[E0030]: lower range bound must be less than or equal to upper
22
--> $DIR/E0030-teach.rs:5:9
33
|
44
LL | 1000 ..= 5 => {}
5-
| ^^^^ lower bound larger than upper bound
5+
| ^^^^^^^^^^ lower bound larger than upper bound
66
|
77
= note: When matching against a range, the compiler verifies that the range is non-empty. Range patterns include both end-points, so this is equivalent to requiring the start of the range to be less than or equal to the end of the range.
88

0 commit comments

Comments
 (0)