Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Integer overflow checking logic is incorrect #91

Open
ivan-shrimp opened this issue Dec 30, 2022 · 0 comments
Open

[BUG] Integer overflow checking logic is incorrect #91

ivan-shrimp opened this issue Dec 30, 2022 · 0 comments
Assignees
Labels
bug Something isn't working

Comments

@ivan-shrimp
Copy link

Description

The current overflow checking logic for integers cannot check for all numeric overflow, as shown in this failing test case:

// 356 cannot be stored in a `u8`, so this should return an error
lexical::parse::<u8, _>("357").unwrap_err();

Specifically, the problem seems to be this claim in the algorithm description:

We have numerical overflow on two cases: we parsed more digits than we theoretically could, or we parsed the same number as the maximum, but the number wrapped. Since the number wrapping will always produce a smaller value than the minimum value for that number of digits, this is a simple comparison.

In the test case above, 357 would wrap to 101, which is not smaller than the minimum value (100) for the maximum number of digits (3 digits for u8).

Prerequisites

Here are a few things you should provide to help me understand the issue:

  • Rust version: rustc 1.68.0-nightly (ad8ae0504 2022-12-29)
  • lexical version: 6.1.1
  • lexical compilation features used: default

Test case

fn main() {
    lexical::parse::<u8, _>("354").unwrap_err(); // pass
    lexical::parse::<u8, _>("355").unwrap_err(); // pass
    lexical::parse::<u8, _>("356").unwrap_err(); // fail, returns Ok(100)
    lexical::parse::<u8, _>("357").unwrap_err(); // fail, returns Ok(101)
    lexical::parse::<u8, _>("358").unwrap_err(); // fail, returns Ok(102)
                                                 // ...
    lexical::parse::<u8, _>("510").unwrap_err(); // fail, returns Ok(254)
    lexical::parse::<u8, _>("511").unwrap_err(); // fail, returns Ok(255)
    lexical::parse::<u8, _>("512").unwrap_err(); // pass
    lexical::parse::<u8, _>("513").unwrap_err(); // pass
    lexical::parse::<u8, _>("514").unwrap_err(); // pass

    lexical::parse::<u8, _>("612").unwrap_err(); // fail, returns Ok(100)
    lexical::parse::<u8, _>("999").unwrap_err(); // fail, returns Ok(231)
    lexical::parse::<u8, _>("1000").unwrap_err(); // pass

    let n = u32::MAX as u64 + 1_000_000_000;
    lexical::parse::<u32, _>((n - 1).to_string()).unwrap_err(); // pass
    lexical::parse::<u32, _>(n.to_string()).unwrap_err(); // fail
    lexical::parse::<u32, _>((n + 1).to_string()).unwrap_err(); // fail

    lexical::parse::<i8, _>("357").unwrap_err(); // fail, returns Ok(101)
}

Using lexical-parse-integer 0.8.6 directly:

fn main() {
    use lexical_parse_integer::FromLexical;

    u8::from_lexical(b"357").unwrap_err(); // fail, returns Ok(101)
}

Additional context

The incorrect logic seems to be implemented here:

if T::IS_SIGNED {
// Signed type: have to deal with 2's complement.
let max_value: U = as_cast::<U, _>(T::MAX) + U::ONE;
if count > max
|| (count == max
&& (value < min_value || value > max_value || (!is_negative && value == max_value)))
{
// Must have overflowed, or wrapped.
// 1. Guaranteed overflow due to too many digits.
// 2. Guaranteed overflow due to wrap.
// 3. Guaranteed overflow since it's too large for the signed type.
// 4. Guaranteed overflow due to 2's complement.
return true;
}
} else if count > max || (count == max && value < min_value) {
// Must have overflowed: too many digits or wrapped.
return true;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants