Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is signed integer overflow in safe Rust in release mode considered as undefined behavior?

Rust treats signed integer overflow differently in debug and release mode. When it happens, Rust panics in debug mode while silently performs two's complement wrapping in release mode.

As far as I know, C/C++ treats signed integer overflow as undefined behavior partly because:

  1. At that time of C's standardization, different underlying architecture of representing signed integers, such as one's complement, might still be in use somewhere. Compilers cannot make assumptions of how overflow is handled in the hardware.
  2. Later compilers thus making assumptions such as the sum of two positive integers must also be positive to generate optimized machine code.

So if Rust compilers do perform the same kind of optimization as C/C++ compilers regarding signed integers, why does The Rustonomicon states:

No matter what, Safe Rust can't cause Undefined Behavior.

Or even if Rust compilers do not perform such optimization, Rust programmers still do not anticipate seeing a signed integer wrapping around. Can't it be called "undefined behavior"?

like image 334
Zhiyao Avatar asked Feb 15 '20 10:02

Zhiyao


1 Answers

Q: So if Rust compilers do perform the same kind of optimization as C/C++ compilers regarding signed integers

Rust does not. Because, as you noticed, it cannot perform these optimizations as integer overflows are well defined.

For an addition in release mode, Rust will emit the following LLVM instruction (you can check on Playground):

add i32 %b, %a

On the other hand, clang will emit the following LLVM instruction (you can check via clang -S -emit-llvm add.c):

add nsw i32 %6, %8

The difference is the nsw (no signed wrap) flag. As specified in the LLVM reference about add:

If the sum has unsigned overflow, the result returned is the mathematical result modulo 2n, where n is the bit width of the result.

Because LLVM integers use a two’s complement representation, this instruction is appropriate for both signed and unsigned integers.

nuw and nsw stand for “No Unsigned Wrap” and “No Signed Wrap”, respectively. If the nuw and/or nsw keywords are present, the result value of the add is a poison value if unsigned and/or signed overflow, respectively, occurs.

The poison value is what leads to undefined behavior. If the flags are not present, the result is well defined as 2's complement wrapping.


Q: Or even if Rust compilers do not perform such optimization, Rust programmers still do not anticipate seeing a signed integer wrapping around. Can't it be called "undefined behavior"?

"Undefined behavior" as used in this context has a very specific meaning that is different from the intuitive English meaning of the two words. UB here specifically means that the compiler can assume an overflow will never happen and that if an overflow will happen, any program behavior is allowed. That's not what Rust specifies.

However, an integer overflow via the arithmetic operators is considered a bug in Rust. That's because, as you said, it is usually not anticipated. If you intentionally want the wrapping behavior, there are methods such as i32::wrapping_add.


Some additional resources:

  • RFC 560 specifies everything about integer overflows in Rust. In short: panic in debug mode, 2's complement wrap in release mode.
  • Myths and Legends about Integer Overflow in Rust. Nice blog post about this topic.
like image 55
Lukas Kalbertodt Avatar answered Nov 15 '22 11:11

Lukas Kalbertodt