mrustc
mrustc copied to clipboard
Signed integer overflow causes undefined behaviour
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.
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.
Signed integer overflow in Rust is supposed to wrap
Not exactly. Integer overflow (both signed and unsigned) in Rust is invalid. That is, if your code causes an integer overflow, then your code is defective. Whether or not the language catches you doing this is up to the compiler options. By default, in debug builds, overflow reliably causes a panic. In release builds, the behavior is that overflow occurs, and you get whatever you get. This just happens to be wrapping arithmetic, but this is not a guarantee of the language or compiler. The behavior, even in release builds, can be changed by enabling overflow checks.
If your code overflows, it is broken. You should never rely on overflow wrapping. If you want wrapping behavior, then you should use the wrapping operators:
i = i.overflowing_add(1);
See:
https://github.com/rust-lang/rfcs/issues/359 http://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/
On Sat, Jun 8, 2019 at 4:20 PM Serentty [email protected] wrote:
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.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/thepowersgang/mrustc/issues/117?email_source=notifications&email_token=ADLILBDBAIJVGO4EVZRZW3LPZQ5CXA5CNFSM4HWIJRC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXIAN5Q#issuecomment-500172534, or mute the thread https://github.com/notifications/unsubscribe-auth/ADLILBCDB2HQSLWFJN7EHELPZQ5CXANCNFSM4HWIJRCQ .
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.
"It may be X or it may be Y" is not something you can rely on, though. If you're writing a library and publishing it as a crate, you don't have control over the compiler flags that are used. So you shouldn't rely on one behavior or the other -- you should express the semantics you want, by using the overflowing_xxx() operations. Yes, they're more verbose, but they are precise.
On Mon, Jun 10, 2019 at 11:50 AM bjorn3 [email protected] wrote:
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 https://doc.rust-lang.org/reference/behavior-not-considered-unsafe.html:
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.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/thepowersgang/mrustc/issues/117?email_source=notifications&email_token=ADLILBHMYGPWWDJ5FAQJL2TPZ2O5TA5CNFSM4HWIJRC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXK3HAA#issuecomment-500544384, or mute the thread https://github.com/notifications/unsubscribe-auth/ADLILBCEN2C4V6LCJNT4PVDPZ2O5TANCNFSM4HWIJRCQ .
"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.
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.
We can solve this in two different ways:
- For GCC and Clang, we can use
-fwrapvin release builds. - On other platforms, we can add runtime checks that unconditionally panic.
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).