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

Signed integer overflow causes undefined behaviour #117

Open
Serentty opened this issue Jun 8, 2019 · 8 comments
Open

Signed integer overflow causes undefined behaviour #117

Serentty opened this issue Jun 8, 2019 · 8 comments

Comments

@Serentty
Copy link

Serentty commented Jun 8, 2019

Signed integer overflow in Rust is supposed to wrap, except in debug builds where it panics to warn developers of possibly unintentional overflow. However, when the following code is compiled into C, the overflow is left as-is, leaving it at the mercy of the C compiler to be optimized in ways that aren't allowed in Rust.

fn main() {
    let mut i = 0i32;
    while i > -1 {
        i += 1;
    }
    println!("Done");
}

When compiled with the reference implementation, this prints “Done”, in accordance with the defined behaviour for signed wrapping in Rust. However, when compiled into C and then compiled with GCC, it optimizes the entire main function away, and leaves just an infinite loop.

@Serentty
Copy link
Author

Serentty commented Jun 8, 2019

I have some good news. I was able to trick the compiler into treating signed overflow as wrapping using the following C code, without requiring -fwrapv.

typedef union {
    signed s;
    unsigned u;
} sint;

int count_up()
{
    sint i;
    i.s = 0;
    while(i.s > -1) {
        i.u += 1;
    }
    return i.s;
}

int count_up_ub()
{
    int i;
    i = 0;
    while(i > -1) {
        i += 1;
    }
    return i;
}

I wonder if this technique could be used by the C code generator.

@arlied-google
Copy link
Contributor

arlied-google commented Jun 10, 2019 via email

@bjorn3
Copy link
Contributor

bjorn3 commented Jun 10, 2019

This just happens to be wrapping arithmetic, but this is not a guarantee of the language or compiler.

I believe it is guaranteed to either panic or wrap around on overflow.

Quoting the reference:

When the programmer has enabled debug_assert! assertions (for example, by enabling a non-optimized build), implementations must insert dynamic checks that panic on overflow. Other kinds of builds may result in panics or silently wrapped values on overflow, at the implementation's discretion.

In the case of implicitly-wrapped overflow, implementations must provide well-defined (even if still considered erroneous) results by using two's complement overflow conventions.

@arlied-google
Copy link
Contributor

arlied-google commented Jun 10, 2019 via email

@bjorn3
Copy link
Contributor

bjorn3 commented Jun 10, 2019

"It may be X or it may be Y" is not something you can rely on, though.

You can rely on the fact that it isn't UB.

@Serentty
Copy link
Author

As far as I understand, overflow in Rust is defined to either panic or wrap. Which it does is not defined, but it is defined that it does one of those two, and which actually happens depends on compiler flags. What is not allowed by the language standard is for the compiler to assume that overflow will not occur, the way that the C language standard allows. This means that optimizations based on the assumption that overflow will not occur are not valid.

@DemiMarie
Copy link

We can solve this in two different ways:

  • For GCC and Clang, we can use -fwrapv in release builds.
  • On other platforms, we can add runtime checks that unconditionally panic.

@thepowersgang
Copy link
Owner

While adding -fwrapv to the default gcc arguments would be the easiest approach, there doesn't seem to be an equivalent option for MSVC (well, not an officially supported one).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants