Skip to content

Commit 695c907

Browse files
committedFeb 6, 2016
Auto merge of #31410 - rkruppe:issue31109, r=pnkfelix
Issue #31109 uncovered two semi-related problems: * A panic in `str::parse::<f64>` * A panic in `rustc::middle::const_eval::lit_to_const` where the result of float parsing was unwrapped. This series of commits fixes both issues and also drive-by-fixes some things I noticed while tracking down the parsing panic.
2 parents 35635ae + a76cb45 commit 695c907

File tree

17 files changed

+119
-41
lines changed

17 files changed

+119
-41
lines changed
 

‎src/etc/test-float-parse/_common.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::mem::transmute;
1616
#[allow(dead_code)]
1717
pub const SEED: [u32; 3] = [0x243f_6a88, 0x85a3_08d3, 0x1319_8a2e];
1818

19-
pub fn validate(text: String) {
19+
pub fn validate(text: &str) {
2020
let mut out = io::stdout();
2121
let x: f64 = text.parse().unwrap();
2222
let f64_bytes: u64 = unsafe { transmute(x) };

‎src/etc/test-float-parse/few-ones.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn main() {
2020
for a in &pow {
2121
for b in &pow {
2222
for c in &pow {
23-
validate((a | b | c).to_string());
23+
validate(&(a | b | c).to_string());
2424
}
2525
}
2626
}

‎src/etc/test-float-parse/huge-pow10.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use _common::validate;
1515
fn main() {
1616
for e in 300..310 {
1717
for i in 0..100000 {
18-
validate(format!("{}e{}", i, e));
18+
validate(&format!("{}e{}", i, e));
1919
}
2020
}
2121
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
mod _common;
12+
13+
use std::char;
14+
use _common::validate;
15+
16+
fn main() {
17+
for n in 0..10 {
18+
let digit = char::from_digit(n, 10).unwrap();
19+
let mut s = "0.".to_string();
20+
for _ in 0..400 {
21+
s.push(digit);
22+
if s.parse::<f64>().is_ok() {
23+
validate(&s);
24+
}
25+
}
26+
}
27+
}

‎src/etc/test-float-parse/many-digits.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ fn main() {
2323
let mut rnd = IsaacRng::from_seed(&SEED);
2424
let mut range = Range::new(0, 10);
2525
for _ in 0..5_000_000u64 {
26-
let num_digits = rnd.gen_range(100, 300);
26+
let num_digits = rnd.gen_range(100, 400);
2727
let digits = gen_digits(num_digits, &mut range, &mut rnd);
28-
validate(digits);
28+
validate(&digits);
2929
}
3030
}
3131

‎src/etc/test-float-parse/rand-f64.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn main() {
2525
let bits = rnd.next_u64();
2626
let x: f64 = unsafe { transmute(bits) };
2727
if x.is_finite() {
28-
validate(format!("{:e}", x));
28+
validate(&format!("{:e}", x));
2929
i += 1;
3030
}
3131
}

‎src/etc/test-float-parse/runtests.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
2222
The actual tests (generating decimal strings and feeding them to dec2flt) is
2323
performed by a set of stand-along rust programs. This script compiles, runs,
24-
and supervises them. In particular, the programs report the strings they
25-
generate and the floating point numbers they converted those strings to.
24+
and supervises them. The programs report the strings they generate and the
25+
floating point numbers they converted those strings to, and this script
26+
checks that the results are correct.
2627
2728
You can run specific tests rather than all of them by giving their names
2829
(without .rs extension) as command line parameters.
@@ -64,9 +65,9 @@
6465
exit code that's not 0, the test fails.
6566
The output on stdout is treated as (f64, f32, decimal) record, encoded thusly:
6667
67-
- The first eight bytes are a binary64 (native endianness).
68-
- The following four bytes are a binary32 (native endianness).
69-
- Then the corresponding string input follows, in ASCII (no newline).
68+
- First, the bits of the f64 encoded as an ASCII hex string.
69+
- Second, the bits of the f32 encoded as an ASCII hex string.
70+
- Then the corresponding string input, in ASCII
7071
- The record is terminated with a newline.
7172
7273
Incomplete records are an error. Not-a-Number bit patterns are invalid too.

‎src/etc/test-float-parse/short-decimals.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ fn main() {
2222
if i % 10 == 0 {
2323
continue;
2424
}
25-
validate(format!("{}e{}", i, e));
26-
validate(format!("{}e-{}", i, e));
25+
validate(&format!("{}e{}", i, e));
26+
validate(&format!("{}e-{}", i, e));
2727
}
2828
}
2929
}

‎src/etc/test-float-parse/subnorm.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use _common::validate;
1616
fn main() {
1717
for bits in 0u32..(1 << 21) {
1818
let single: f32 = unsafe { transmute(bits) };
19-
validate(format!("{:e}", single));
19+
validate(&format!("{:e}", single));
2020
let double: f64 = unsafe { transmute(bits as u64) };
21-
validate(format!("{:e}", double));
21+
validate(&format!("{:e}", double));
2222
}
2323
}

‎src/etc/test-float-parse/tiny-pow10.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use _common::validate;
1515
fn main() {
1616
for e in 301..327 {
1717
for i in 0..100000 {
18-
validate(format!("{}e-{}", i, e));
18+
validate(&format!("{}e-{}", i, e));
1919
}
2020
}
2121
}

‎src/etc/test-float-parse/u32-small.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ use _common::validate;
1414

1515
fn main() {
1616
for i in 0..(1 << 19) {
17-
validate(i.to_string());
17+
validate(&i.to_string());
1818
}
1919
}

‎src/etc/test-float-parse/u64-pow2.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ use std::u64;
1616
fn main() {
1717
for exp in 19..64 {
1818
let power: u64 = 1 << exp;
19-
validate(power.to_string());
19+
validate(&power.to_string());
2020
for offset in 1..123 {
21-
validate((power + offset).to_string());
22-
validate((power - offset).to_string());
21+
validate(&(power + offset).to_string());
22+
validate(&(power - offset).to_string());
2323
}
2424
}
2525
for offset in 0..123 {
26-
validate((u64::MAX - offset).to_string());
26+
validate(&(u64::MAX - offset).to_string());
2727
}
2828
}

‎src/libcore/num/dec2flt/algorithm.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ fn algorithm_r<T: RawFloat>(f: &Big, e: i16, z0: T) -> T {
127127
// This is written a bit awkwardly because our bignums don't support
128128
// negative numbers, so we use the absolute value + sign information.
129129
// The multiplication with m_digits can't overflow. If `x` or `y` are large enough that
130-
// we need to worry about overflow, then they are also large enough that`make_ratio` has
130+
// we need to worry about overflow, then they are also large enough that `make_ratio` has
131131
// reduced the fraction by a factor of 2^64 or more.
132132
let (d2, d_negative) = if x >= y {
133133
// Don't need x any more, save a clone().
@@ -278,7 +278,7 @@ fn quick_start<T: RawFloat>(u: &mut Big, v: &mut Big, k: &mut i16) {
278278
// The target ratio is one where u/v is in an in-range significand. Thus our termination
279279
// condition is log2(u / v) being the significand bits, plus/minus one.
280280
// FIXME Looking at the second bit could improve the estimate and avoid some more divisions.
281-
let target_ratio = f64::sig_bits() as i16;
281+
let target_ratio = T::sig_bits() as i16;
282282
let log2_u = u.bit_length() as i16;
283283
let log2_v = v.bit_length() as i16;
284284
let mut u_shift: i16 = 0;

‎src/libcore/num/dec2flt/mod.rs

+28-8
Original file line numberDiff line numberDiff line change
@@ -230,18 +230,15 @@ fn convert<T: RawFloat>(mut decimal: Decimal) -> Result<T, ParseFloatError> {
230230
if let Some(x) = trivial_cases(&decimal) {
231231
return Ok(x);
232232
}
233-
// AlgorithmM and AlgorithmR both compute approximately `f * 10^e`.
234-
let max_digits = decimal.integral.len() + decimal.fractional.len() +
235-
decimal.exp.abs() as usize;
236233
// Remove/shift out the decimal point.
237234
let e = decimal.exp - decimal.fractional.len() as i64;
238235
if let Some(x) = algorithm::fast_path(decimal.integral, decimal.fractional, e) {
239236
return Ok(x);
240237
}
241238
// Big32x40 is limited to 1280 bits, which translates to about 385 decimal digits.
242-
// If we exceed this, perhaps while calculating `f * 10^e` in Algorithm R or Algorithm M,
243-
// we'll crash. So we error out before getting too close, with a generous safety margin.
244-
if max_digits > 375 {
239+
// If we exceed this, we'll crash, so we error out before getting too close (within 10^10).
240+
let upper_bound = bound_intermediate_digits(&decimal, e);
241+
if upper_bound > 375 {
245242
return Err(pfe_invalid());
246243
}
247244
let f = digits_to_big(decimal.integral, decimal.fractional);
@@ -251,7 +248,7 @@ fn convert<T: RawFloat>(mut decimal: Decimal) -> Result<T, ParseFloatError> {
251248
// FIXME These bounds are rather conservative. A more careful analysis of the failure modes
252249
// of Bellerophon could allow using it in more cases for a massive speed up.
253250
let exponent_in_range = table::MIN_E <= e && e <= table::MAX_E;
254-
let value_in_range = max_digits <= T::max_normal_digits();
251+
let value_in_range = upper_bound <= T::max_normal_digits() as u64;
255252
if exponent_in_range && value_in_range {
256253
Ok(algorithm::bellerophon(&f, e))
257254
} else {
@@ -288,13 +285,36 @@ fn simplify(decimal: &mut Decimal) {
288285
}
289286
}
290287

288+
/// Quick and dirty upper bound on the size (log10) of the largest value that Algorithm R and
289+
/// Algorithm M will compute while working on the given decimal.
290+
fn bound_intermediate_digits(decimal: &Decimal, e: i64) -> u64 {
291+
// We don't need to worry too much about overflow here thanks to trivial_cases() and the
292+
// parser, which filter out the most extreme inputs for us.
293+
let f_len: u64 = decimal.integral.len() as u64 + decimal.fractional.len() as u64;
294+
if e >= 0 {
295+
// In the case e >= 0, both algorithms compute about `f * 10^e`. Algorithm R proceeds to
296+
// do some complicated calculations with this but we can ignore that for the upper bound
297+
// because it also reduces the fraction beforehand, so we have plenty of buffer there.
298+
f_len + (e as u64)
299+
} else {
300+
// If e < 0, Algorithm R does roughly the same thing, but Algorithm M differs:
301+
// It tries to find a positive number k such that `f << k / 10^e` is an in-range
302+
// significand. This will result in about `2^53 * f * 10^e` < `10^17 * f * 10^e`.
303+
// One input that triggers this is 0.33...33 (375 x 3).
304+
f_len + (e.abs() as u64) + 17
305+
}
306+
}
307+
291308
/// Detect obvious overflows and underflows without even looking at the decimal digits.
292309
fn trivial_cases<T: RawFloat>(decimal: &Decimal) -> Option<T> {
293310
// There were zeros but they were stripped by simplify()
294311
if decimal.integral.is_empty() && decimal.fractional.is_empty() {
295312
return Some(T::zero());
296313
}
297-
// This is a crude approximation of ceil(log10(the real value)).
314+
// This is a crude approximation of ceil(log10(the real value)). We don't need to worry too
315+
// much about overflow here because the input length is tiny (at least compared to 2^64) and
316+
// the parser already handles exponents whose absolute value is greater than 10^18
317+
// (which is still 10^19 short of 2^64).
298318
let max_place = decimal.exp + decimal.integral.len() as i64;
299319
if max_place > T::inf_cutoff() {
300320
return Some(T::infinity());

‎src/libcoretest/num/dec2flt/mod.rs

+16-7
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,11 @@ macro_rules! test_literal {
2525
let x64: f64 = $x;
2626
let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
2727
for input in inputs {
28-
if input != "inf" {
29-
assert_eq!(input.parse(), Ok(x64));
30-
assert_eq!(input.parse(), Ok(x32));
31-
let neg_input = &format!("-{}", input);
32-
assert_eq!(neg_input.parse(), Ok(-x64));
33-
assert_eq!(neg_input.parse(), Ok(-x32));
34-
}
28+
assert_eq!(input.parse(), Ok(x64));
29+
assert_eq!(input.parse(), Ok(x32));
30+
let neg_input = &format!("-{}", input);
31+
assert_eq!(neg_input.parse(), Ok(-x64));
32+
assert_eq!(neg_input.parse(), Ok(-x32));
3533
}
3634
})
3735
}
@@ -136,6 +134,17 @@ fn massive_exponent() {
136134
assert_eq!(format!("1e{}000", max).parse(), Ok(f64::INFINITY));
137135
}
138136

137+
#[test]
138+
fn borderline_overflow() {
139+
let mut s = "0.".to_string();
140+
for _ in 0..375 {
141+
s.push('3');
142+
}
143+
// At the time of this writing, this returns Err(..), but this is a bug that should be fixed.
144+
// It makes no sense to enshrine that in a test, the important part is that it doesn't panic.
145+
let _ = s.parse::<f64>();
146+
}
147+
139148
#[bench]
140149
fn bench_0(b: &mut test::Bencher) {
141150
b.iter(|| "0.0".parse::<f64>());

‎src/librustc/middle/const_eval.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use middle::ty::{self, Ty};
2626
use middle::astconv_util::ast_ty_to_prim_ty;
2727
use util::num::ToPrimitive;
2828
use util::nodemap::NodeMap;
29+
use session::Session;
2930

3031
use graphviz::IntoCow;
3132
use syntax::ast;
@@ -1117,7 +1118,7 @@ pub fn eval_const_expr_partial<'tcx>(tcx: &ty::ctxt<'tcx>,
11171118
debug!("const call({:?})", call_args);
11181119
try!(eval_const_expr_partial(tcx, &**result, ty_hint, Some(&call_args)))
11191120
},
1120-
hir::ExprLit(ref lit) => lit_to_const(&**lit, ety),
1121+
hir::ExprLit(ref lit) => lit_to_const(tcx.sess, e.span, &**lit, ety),
11211122
hir::ExprBlock(ref block) => {
11221123
match block.expr {
11231124
Some(ref expr) => try!(eval_const_expr_partial(tcx, &**expr, ty_hint, fn_args)),
@@ -1319,7 +1320,7 @@ fn cast_const<'tcx>(tcx: &ty::ctxt<'tcx>, val: ConstVal, ty: Ty) -> CastResult {
13191320
}
13201321
}
13211322

1322-
fn lit_to_const(lit: &ast::Lit, ty_hint: Option<Ty>) -> ConstVal {
1323+
fn lit_to_const(sess: &Session, span: Span, lit: &ast::Lit, ty_hint: Option<Ty>) -> ConstVal {
13231324
match lit.node {
13241325
ast::LitStr(ref s, _) => Str((*s).clone()),
13251326
ast::LitByteStr(ref data) => {
@@ -1339,7 +1340,12 @@ fn lit_to_const(lit: &ast::Lit, ty_hint: Option<Ty>) -> ConstVal {
13391340
ast::LitInt(n, ast::UnsignedIntLit(_)) => Uint(n),
13401341
ast::LitFloat(ref n, _) |
13411342
ast::LitFloatUnsuffixed(ref n) => {
1342-
Float(n.parse::<f64>().unwrap() as f64)
1343+
if let Ok(x) = n.parse::<f64>() {
1344+
Float(x)
1345+
} else {
1346+
// FIXME(#31407) this is only necessary because float parsing is buggy
1347+
sess.span_bug(span, "could not evaluate float literal (see issue #31407)");
1348+
}
13431349
}
13441350
ast::LitBool(b) => Bool(b)
13451351
}

‎src/test/compile-fail/issue-31109.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
// FIXME(#31407) this error should go away, but in the meantime we test that it
13+
// is accompanied by a somewhat useful error message.
14+
let _: f64 = 1234567890123456789012345678901234567890e-340; //~ ERROR could not evaluate float
15+
}

0 commit comments

Comments
 (0)
Please sign in to comment.