Skip to content

Commit 857f5dd

Browse files
committed
Don't inline integer literals when out of range
1 parent 78bc0a5 commit 857f5dd

File tree

3 files changed

+171
-77
lines changed

3 files changed

+171
-77
lines changed

compiler/rustc_ast_lowering/src/format.rs

+123-77
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ use rustc_span::{
1212
};
1313
use std::borrow::Cow;
1414

15+
fn int_ty_max(int_ty: IntTy) -> Option<u128> {
16+
match int_ty {
17+
// isize is platform-dependent, so we should use
18+
// TyCtxt.data_layout.pointer_size
19+
// This is available, for example, from a LoweringContext
20+
IntTy::Isize => None,
21+
IntTy::I8 => Some(i8::MAX as u128),
22+
IntTy::I16 => Some(i16::MAX as u128),
23+
IntTy::I32 => Some(i32::MAX as u128),
24+
IntTy::I64 => Some(i64::MAX as u128),
25+
IntTy::I128 => Some(i128::MAX as u128),
26+
}
27+
}
28+
29+
fn uint_ty_max(uint_ty: UintTy) -> Option<u128> {
30+
match uint_ty {
31+
// usize is platform-dependent, so we should use
32+
// TyCtxt.data_layout.pointer_size
33+
// This is available, for example, from a LoweringContext
34+
UintTy::Usize => None,
35+
UintTy::U8 => Some(u8::MAX as u128),
36+
UintTy::U16 => Some(u16::MAX as u128),
37+
UintTy::U32 => Some(u32::MAX as u128),
38+
UintTy::U64 => Some(u64::MAX as u128),
39+
UintTy::U128 => Some(u128::MAX as u128),
40+
}
41+
}
42+
1543
impl<'hir> LoweringContext<'_, 'hir> {
1644
pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> {
1745
// Never call the const constructor of `fmt::Arguments` if the
@@ -20,10 +48,104 @@ impl<'hir> LoweringContext<'_, 'hir> {
2048
let mut fmt = Cow::Borrowed(fmt);
2149
if self.tcx.sess.opts.unstable_opts.flatten_format_args {
2250
fmt = flatten_format_args(fmt);
23-
fmt = inline_literals(fmt);
51+
fmt = self.inline_literals(fmt);
2452
}
2553
expand_format_args(self, sp, &fmt, allow_const)
2654
}
55+
56+
/// Try to convert a literal into an interned string
57+
fn try_inline_lit(&self, lit: token::Lit) -> Option<Symbol> {
58+
match LitKind::from_token_lit(lit) {
59+
Ok(LitKind::Str(s, _)) => Some(s),
60+
Ok(LitKind::Int(n, ty)) => {
61+
// platform-dependent usize and isize MAX
62+
let usize_bits = self.tcx.data_layout.pointer_size.bits();
63+
let usize_max = if usize_bits >= 128 { u128::MAX } else { 1u128 << usize_bits - 1 };
64+
let isize_max = usize_max >> 1;
65+
match ty {
66+
// unsuffixed integer literals are assumed to be i32's
67+
LitIntType::Unsuffixed => (n <= i32::MAX as u128).then_some(Symbol::intern(&n.to_string())),
68+
LitIntType::Signed(int_ty) => {
69+
let max_literal = int_ty_max(int_ty).unwrap_or(isize_max);
70+
(n <= max_literal).then_some(Symbol::intern(&n.to_string()))
71+
}
72+
LitIntType::Unsigned(uint_ty) => {
73+
let max_literal = uint_ty_max(uint_ty).unwrap_or(usize_max);
74+
(n <= max_literal).then_some(Symbol::intern(&n.to_string()))
75+
}
76+
}
77+
}
78+
_ => None,
79+
}
80+
}
81+
82+
/// Inline literals into the format string.
83+
///
84+
/// Turns
85+
///
86+
/// `format_args!("Hello, {}! {} {}", "World", 123, x)`
87+
///
88+
/// into
89+
///
90+
/// `format_args!("Hello, World! 123 {}", x)`.
91+
fn inline_literals<'fmt>(&self, mut fmt: Cow<'fmt, FormatArgs>) -> Cow<'fmt, FormatArgs> {
92+
let mut was_inlined = vec![false; fmt.arguments.all_args().len()];
93+
let mut inlined_anything = false;
94+
95+
for i in 0..fmt.template.len() {
96+
let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue };
97+
let Ok(arg_index) = placeholder.argument.index else { continue };
98+
99+
let mut literal = None;
100+
101+
if let FormatTrait::Display = placeholder.format_trait
102+
&& placeholder.format_options == Default::default()
103+
&& let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
104+
&& let ExprKind::Lit(lit) = arg.kind
105+
{
106+
literal = self.try_inline_lit(lit);
107+
}
108+
109+
if let Some(literal) = literal {
110+
// Now we need to mutate the outer FormatArgs.
111+
// If this is the first time, this clones the outer FormatArgs.
112+
let fmt = fmt.to_mut();
113+
// Replace the placeholder with the literal.
114+
fmt.template[i] = FormatArgsPiece::Literal(literal);
115+
was_inlined[arg_index] = true;
116+
inlined_anything = true;
117+
}
118+
}
119+
120+
// Remove the arguments that were inlined.
121+
if inlined_anything {
122+
let fmt = fmt.to_mut();
123+
124+
let mut remove = was_inlined;
125+
126+
// Don't remove anything that's still used.
127+
for_all_argument_indexes(&mut fmt.template, |index| remove[*index] = false);
128+
129+
// Drop all the arguments that are marked for removal.
130+
let mut remove_it = remove.iter();
131+
fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&true));
132+
133+
// Calculate the mapping of old to new indexes for the remaining arguments.
134+
let index_map: Vec<usize> = remove
135+
.into_iter()
136+
.scan(0, |i, remove| {
137+
let mapped = *i;
138+
*i += !remove as usize;
139+
Some(mapped)
140+
})
141+
.collect();
142+
143+
// Correct the indexes that refer to arguments that have shifted position.
144+
for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]);
145+
}
146+
147+
fmt
148+
}
27149
}
28150

29151
/// Flattens nested `format_args!()` into one.
@@ -103,82 +225,6 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> {
103225
fmt
104226
}
105227

106-
/// Inline literals into the format string.
107-
///
108-
/// Turns
109-
///
110-
/// `format_args!("Hello, {}! {} {}", "World", 123, x)`
111-
///
112-
/// into
113-
///
114-
/// `format_args!("Hello, World! 123 {}", x)`.
115-
fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> {
116-
let mut was_inlined = vec![false; fmt.arguments.all_args().len()];
117-
let mut inlined_anything = false;
118-
119-
for i in 0..fmt.template.len() {
120-
let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue };
121-
let Ok(arg_index) = placeholder.argument.index else { continue };
122-
123-
let mut literal = None;
124-
125-
if let FormatTrait::Display = placeholder.format_trait
126-
&& placeholder.format_options == Default::default()
127-
&& let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs()
128-
&& let ExprKind::Lit(lit) = arg.kind
129-
{
130-
if let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind
131-
&& let Ok(LitKind::Str(s, _)) = LitKind::from_token_lit(lit)
132-
{
133-
literal = Some(s);
134-
} else if let token::LitKind::Integer = lit.kind
135-
&& let Ok(LitKind::Int(n, _)) = LitKind::from_token_lit(lit)
136-
{
137-
literal = Some(Symbol::intern(&n.to_string()));
138-
}
139-
}
140-
141-
if let Some(literal) = literal {
142-
// Now we need to mutate the outer FormatArgs.
143-
// If this is the first time, this clones the outer FormatArgs.
144-
let fmt = fmt.to_mut();
145-
// Replace the placeholder with the literal.
146-
fmt.template[i] = FormatArgsPiece::Literal(literal);
147-
was_inlined[arg_index] = true;
148-
inlined_anything = true;
149-
}
150-
}
151-
152-
// Remove the arguments that were inlined.
153-
if inlined_anything {
154-
let fmt = fmt.to_mut();
155-
156-
let mut remove = was_inlined;
157-
158-
// Don't remove anything that's still used.
159-
for_all_argument_indexes(&mut fmt.template, |index| remove[*index] = false);
160-
161-
// Drop all the arguments that are marked for removal.
162-
let mut remove_it = remove.iter();
163-
fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&true));
164-
165-
// Calculate the mapping of old to new indexes for the remaining arguments.
166-
let index_map: Vec<usize> = remove
167-
.into_iter()
168-
.scan(0, |i, remove| {
169-
let mapped = *i;
170-
*i += !remove as usize;
171-
Some(mapped)
172-
})
173-
.collect();
174-
175-
// Correct the indexes that refer to arguments that have shifted position.
176-
for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]);
177-
}
178-
179-
fmt
180-
}
181-
182228
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
183229
enum ArgumentType {
184230
Format(FormatTrait),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
format_args!("{}", 0x8f_i8); // issue #115423
3+
//~^ ERROR literal out of range for `i8`
4+
format_args!("{}", 0xffff_ffff_u8); // issue #116633
5+
//~^ ERROR literal out of range for `u8`
6+
format_args!("{}", 0xffff_ffff); // treat unsuffixed literals as i32
7+
//~^ ERROR literal out of range for `i32`
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: literal out of range for `i8`
2+
--> $DIR/no-inline-literals-out-of-range.rs:2:24
3+
|
4+
LL | format_args!("{}", 0x8f_i8); // issue #115423
5+
| ^^^^^^^
6+
|
7+
= note: the literal `0x8f_i8` (decimal `143`) does not fit into the type `i8` and will become `-113i8`
8+
= note: `#[deny(overflowing_literals)]` on by default
9+
help: consider using the type `u8` instead
10+
|
11+
LL | format_args!("{}", 0x8f_u8); // issue #115423
12+
| ~~~~~~~
13+
help: to use as a negative number (decimal `-113`), consider using the type `u8` for the literal and cast it to `i8`
14+
|
15+
LL | format_args!("{}", 0x8f_u8 as i8); // issue #115423
16+
| ~~~~~~~~~~~~~
17+
18+
error: literal out of range for `u8`
19+
--> $DIR/no-inline-literals-out-of-range.rs:4:24
20+
|
21+
LL | format_args!("{}", 0xffff_ffff_u8); // issue #116633
22+
| ^^^^^^^^^^^^^^ help: consider using the type `u32` instead: `0xffff_ffff_u32`
23+
|
24+
= note: the literal `0xffff_ffff_u8` (decimal `4294967295`) does not fit into the type `u8` and will become `255u8`
25+
26+
error: literal out of range for `i32`
27+
--> $DIR/no-inline-literals-out-of-range.rs:6:24
28+
|
29+
LL | format_args!("{}", 0xffff_ffff); // treat unsuffixed literals as i32
30+
| ^^^^^^^^^^^
31+
|
32+
= note: the literal `0xffff_ffff` (decimal `4294967295`) does not fit into the type `i32` and will become `-1i32`
33+
= help: consider using the type `u32` instead
34+
help: to use as a negative number (decimal `-1`), consider using the type `u32` for the literal and cast it to `i32`
35+
|
36+
LL | format_args!("{}", 0xffff_ffffu32 as i32); // treat unsuffixed literals as i32
37+
| ~~~~~~~~~~~~~~~~~~~~~
38+
39+
error: aborting due to 3 previous errors
40+

0 commit comments

Comments
 (0)