llvm / llvm/llvm-project

[clang][CodeGen] InitCatchParam emits an invalid address-space-changing bitcast when catching a pointer in a non-zero address space

Open
#222,931 1 comment 0 reactions 0 assignees View on GitHub
clang:codegen crash-on-invalid
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.