Issue |
136679
|
Summary |
Clang silently eliminates division-by-zero error detection when optimizations are enabled, with inconsistent behavior between optimization levels
|
Labels |
clang
|
Assignees |
|
Reporter |
399833783
|
## Issue Description:
When optimization (-O2) is enabled, Clang optimizes expressions that could cause division-by-zero errors into implementations that don't use division, including division expressions used for multiplication overflow detection and when converting division results to bool values. However, without optimization (-O0), Clang preserves the original division operation and its potential runtime errors.
This inconsistency between optimization levels is not only confusing but can lead to code behaving differently in development versus production environments.
## Issue Reproduction:
### Case A - Multiplication overflow detection
#### Case A1 - No condition check, with -O2
- C++ code:
```c++
bool mulOverflow1(unsigned long long a, unsigned long long b)
{
return a > (~0ULL) / b; // Should trigger division-by-zero when b=0
}
```
- Generated assembly (compile with: clang++ -O2 -S "source file name"):
```assembly
mulOverflow1(unsigned long long, unsigned long long):
mov rax, rsi
mul rdi
seto al
ret
```
Clang optimizes this to multiplication and overflow flag check, no div instruction used.
#### Case A2 - With condition check, with -O2
- C++ code:
```c++
bool mulOverflow2(unsigned long long a, unsigned long long b)
{
if (b == 0) return false; // Correct boundary check
return a > (~0ULL) / b;
}
```
- Generated assembly (compile with: clang++ -O2 -S "source file name"):
```assembly
mulOverflow2(unsigned long long, unsigned long long):
test rsi, rsi ; Redundant instruction
je .LBB0_1 ; Redundant instruction
mov rax, rsi
mul rdi
seto al
ret
.LBB1_1:
xor eax, eax ; Redundant instruction
ret ; Redundant instruction
```
Clang generates the right code with redundant conditional check, which is more complex than case A1 and unnecessary.
#### Case A3 - No condition check, without -O2
- C++ code:
Same code as A1 but compiled with -O0.
- Generated assembly (compile with: clang++ -O0 -S "source file name"):
```assembly
mulOverflow1(unsigned long long, unsigned long long):
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
mov qword ptr [rbp - 16], rsi
mov rax, qword ptr [rbp - 8]
mov qword ptr [rbp - 24], rax
mov rax, -1
xor ecx, ecx
mov edx, ecx
div qword ptr [rbp - 16]
mov rcx, rax
mov rax, qword ptr [rbp - 24]
cmp rax, rcx
seta al
and al, 1
pop rbp
ret
```
Clang preserves the division operation, generates div instruction (correct behavior).
### Case B - Division result to bool
#### Case B1 - No condition check, with -O2
- C++ code:
```c++
bool myDivToBool1(unsigned long long a, unsigned long long b)
{
return bool(a / b); // Should trigger division-by-zero when b=0
}
```
- Generated assembly (compile with: clang++ -O2 -S "source file name"):
```assembly
myDivToBool1(unsigned long long, unsigned long long):
cmp rsi, rdi
setbe al
ret
```
Clang optimizes this to compare the operands, no div instruction used.
#### Case B2 - With condition check, with -O2
- C++ code:
```c++
bool myDivToBool2(unsigned long long a, unsigned long long b)
{
if (b == 0) return true; // Correct boundary check
return bool(a / b);
}
```
- Generated assembly (compile with: clang++ -O2 -S "source file name"):
```assembly
myDivToBool2(unsigned long long, unsigned long long):
cmp rsi, rdi
setbe al
ret
```
Clang generates the right code.
#### Case B3 - No condition check, without -O2
- C++ code:
Same code as B1 but compiled with -O0
- Generated assembly (compile with: clang++ -O0 -S "source file name"):
```assembly
myDivToBool1(unsigned long long, unsigned long long):
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
mov qword ptr [rbp - 16], rsi
mov rax, qword ptr [rbp - 8]
xor ecx, ecx
mov edx, ecx
div qword ptr [rbp - 16]
cmp rax, 0
setne al
and al, 1
pop rbp
ret
```
Clang preserves the division operation, generates div instruction (correct behavior).
## Existing problems:
1. When optimizations are enabled, expected runtime errors are silently eliminated, potentially masking code issues.
2. The same code behaves differently at different optimization levels, making debugging more difficult.
3. For the multiplication overflow detection code with zero checks added (case A2), the generated code is less efficient than the optimized unsafe versions (case A1).
4. Clang's -O0 behavior is correct, but its -O2 behavior violates reasonable expectations about division semantics.
5. When developers rely on division-by-zero errors as defense mechanisms, this optimization creates security risks.
## Expected behavior:
1. Even in optimized mode, the compiler should preserve division-by-zero error detection from the original code, or at least issue a warning.
2. For code that does check if the divisor is 0, the compiler should not generate more complex code than the unchecked version.
3. Behavior should be consistent across different optimization levels.
## Compiler version information:
Clang 20.1.0
## Reference Links:
https://godbolt.org/z/3vKE5jPnj
https://godbolt.org/z/6cs3vPsxq
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs