[clang][CodeGen] InitCatchParam emits an invalid address-space-changing bitcast when catching a pointer in a non-zero address space
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
### Summary
When a `catch` handler's parameter is a pointer whose type lives in a non-zero
address space, `InitCatchParam` casts the `void*` returned by
`__cxa_begin_catch` with `IRBuilder::CreateBitCast`. A `bitcast` may not change
address space, so clang emits IR that its own verifier rejects.
If the two address spaces also differ in pointer *size* (e.g. x86's
`addrspace(270)`, which is 32-bit), the malformed IR reaches the backend and
crashes SelectionDAG.
### Reproducer
No special extensions needed — a plain `address_space` attribute is enough:
```c++
typedef int __attribute__((address_space(1))) *as1_int_ptr;
void sink(int);
void g() { try { throw (as1_int_ptr)0; } catch (as1_int_ptr p) { sink(*p); } }
```
```console
$ clang -cc1 -fcxx-exceptions -fexceptions -emit-llvm -triple x86_64-linux-gnu -o - t.cpp
Invalid bitcast
%exn.casted = bitcast ptr %4 to ptr addrspace(1)
```
Same result on `-triple amdgcn-amd-amdhsa`, and with MS extensions:
```c++
void sink(int);
void g() { try { throw (int * __ptr32)0; } catch (int * __ptr32 p) { sink(*p); } }
```
```console
$ clang -cc1 -fms-extensions -fcxx-exceptions -fexceptions -emit-llvm -triple x86_64-linux-gnu -o - t.cpp
Invalid bitcast
%exn.casted = bitcast ptr %4 to ptr addrspace(270)
Invalid bitcast
%5 = bitcast ptr %4 to ptr addrspace(270)
```
### Crash
A release clang driver passes `-disable-llvm-verifier`, so the invalid IR
silently reaches the backend. With `__ptr32` (`addrspace(270)` is 32-bit, so
the bogus bitcast also changes pointer width):
```console
$ clang -cc1 -fms-extensions -fcxx-exceptions -fexceptions -triple x86_64-linux-gnu \
-O1 -disable-llvm-verifier -emit-obj -o /dev/null t.cpp
Stack dump:
3. Running pass 'Function Pass Manager' on module 't.cpp'.
4. Running pass 'X86 DAG->DAG Instruction Selection' on function '@_Z1gv'
#3 llvm::APIntOps::ScaleBitMask(llvm::APInt const&, unsigned int, bool)
#4 llvm::SelectionDAG::computeKnownBits(llvm::SDValue, llvm::APInt const&, unsigned int) const
#5 llvm::SelectionDAG::computeKnownBits(llvm::SDValue, unsigned int) const
#6 llvm::SelectionDAG::SignBitIsZero(llvm::SDValue, unsigned int) const
#7 (anonymous namespace)::DAGCombiner::visitSIGN_EXTEND(llvm::SDNode*)
```
Exit code 136 (SIGFPE — integer divide-by-zero inside `ScaleBitMask`). The
stack trace blames ISel, but ISel is only the victim; the defect is in CodeGen.
### Root cause
`clang/lib/CodeGen/ItaniumCXXABI.cpp`, `InitCatchParam` — three sites cast the
adjusted exception pointer with `CreateBitCast`:
| Line | Context | Observed to trigger |
|------|---------|---------------------|
| 4997 | catch-by-reference of pointer-to-record, spilled to `exn.byref.tmp` | yes |
| 5006 | binding the by-reference catch parameter (`exn.byref`) | not observed |
| 5020 | catch-by-value of a pointer (`exn.casted`) | yes |
`LLVMCatchTy` / `PtrTy` come from `ConvertTypeForMem` of the catch parameter's
type, so they carry the parameter's address space, while `__cxa_begin_catch`
returns a default-address-space `ptr`.
### Suggested fix
| 4997 | catch-by-reference of pointer-to-record, spilled to `exn.byref.tmp` | yes |
| 5006 | binding the by-reference catch parameter (`exn.byref`) | not observed |
| 5020 | catch-by-value of a pointer (`exn.casted`) | yes |
`LLVMCatchTy` / `PtrTy` come from `ConvertTypeForMem` of the catch parameter's
type, so they carry the parameter's address space, while `__cxa_begin_catch`
returns a default-address-space `ptr`.
### Suggested fix
Use the address-space-aware helper, already the idiom in ~30 other places in
clang CodeGen. It is a no-op when the address spaces match, so behaviour for
default-address-space catches is unchanged:
```diff
- llvm::Value *Casted = CGF.Builder.CreateBitCast(AdjustedExn, PtrTy);
+ llvm::Value *Casted =
+ CGF.Builder.CreatePointerBitCastOrAddrSpaceCast(AdjustedExn, PtrTy);
```
```diff
- llvm::Value *CastExn =
- CGF.Builder.CreateBitCast(AdjustedExn, LLVMCatchTy, "exn.casted");
+ llvm::Value *CastExn = CGF.Builder.CreatePointerBitCastOrAddrSpaceCast(
+ AdjustedExn, LLVMCatchTy, "exn.casted");
```
(and likewise at line 5006).
With that change the reproducers emit `addrspacecast` instead, the module
verifies, and the backend crash goes away.
### Version tested
Reproduced on a downstream build based on upstream
`ca7933e47d3a3451d81e72ac174dcb5aa28b59d1` (22.1.8). The three lines in question
are verbatim upstream and untouched by that fork, and the reproducers use only
upstream features — but I have not run this against trunk or a stock release
build, so please confirm the exact line numbers on current `main`.
Contributor guide
Research direction
Read clang/lib/CodeGen/ItaniumCXXABI.cpp at InitCatchParam and reproduce the issue with the address_space and __ptr32 examples from the report. Check all three adjusted-exception-pointer casts and verify the generated IR with clang -cc1. Done when the non-default address-space catches emit valid IR and the backend crash no longer occurs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100