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

Reply via email to