Issue 183621
Summary [clang] Clang 22 incorrect code for `delete[]` with MS ABI
Labels clang
Assignees
Reporter tycho
    Godbolt showing problem (they lack a Clang 22 release build yet, so I'm using "trunk" on there for this):
https://gcc.godbolt.org/z/YPY6T7bdT

I tried to minify this manually and it's not quite representative of my code, but it still illustrates something going wrong with `delete[]` on the MS ABI.

Code sample:
```
template <class T>
__attribute__((always_inline)) inline void safe_delete_array(T& p) {
    delete[] p;
    p = nullptr;
}

// Non-trivial, non-virtual dtor (forces cookie for arrays).
struct A {
    ~A();
};

// Non-trivial *virtual* dtor (polymorphic).
struct B {
    virtual ~B();
    virtual int f() { return 1; }
};

struct D : B { ~D() override {} };

struct C {
    A* a;
 B* b;
    unsigned int n;

    C(unsigned int n_);
 ~C();
};

C::C(unsigned int n_) : a(nullptr), b(nullptr), n(n_) {
    a = new A[n];
    b = new B[n];
}

C::~C() {
    safe_delete_array(a);
 safe_delete_array(b);
}
```

Clang 21 destructor for struct `C` (looks okay):
```
"??1C@@QEAA@XZ":
        push    r15
        push    r14
 push    rsi
        push    rdi
        push    rbx
        sub rsp, 32
        mov     rsi, rcx
        mov     r14, qword ptr [rcx]
 test    r14, r14
        je      .LBB2_5
        lea     rdi, [r14 - 8]
        mov     rbx, qword ptr [r14 - 8]
        test    rbx, rbx
 je      .LBB2_4
        dec     r14
        mov     r15, rbx
.LBB2_3:
 lea     rcx, [r14 + r15]
        call    "??1A@@QEAA@XZ"
 dec     r15
        jne     .LBB2_3
.LBB2_4:
        add     rbx, 8
 mov     rcx, rdi
        mov     rdx, rbx
        call "??_V@YAXPEAX_K@Z"
.LBB2_5:
        mov     qword ptr [rsi], 0
 mov     rdi, qword ptr [rsi + 8]
        test    rdi, rdi
        je .LBB2_10
        mov     rax, qword ptr [rdi - 8]
        add     rdi, -8
 lea     rbx, [8*rax]
        test    rax, rax
        je .LBB2_9
        mov     r14, rbx
.LBB2_8:
        lea     rcx, [rdi + r14]
        call    "??1B@@UEAA@XZ"
        add     r14, -8
        jne .LBB2_8
.LBB2_9:
        add     rbx, 8
        mov     rcx, rdi
 mov     rdx, rbx
        call    "??_V@YAXPEAX_K@Z"
.LBB2_10:
 mov     qword ptr [rsi + 8], 0
        add     rsp, 32
        pop rbx
        pop     rdi
        pop     rsi
        pop     r14
 pop     r15
        ret
```

Clang 22 destructor for struct `C`:
```
"??1C@@QEAA@XZ":
        push    r15
        push    r14
 push    rsi
        push    rdi
        push    rbx
        sub rsp, 32
        mov     rsi, rcx
        mov     r14, qword ptr [rcx]
 test    r14, r14
        je      .LBB2_5
        lea     rdi, [r14 - 8]
        mov     rbx, qword ptr [r14 - 8]
        test    rbx, rbx
 je      .LBB2_4
        dec     r14
        mov     r15, rbx
.LBB2_3:
 lea     rcx, [r14 + r15]
        call    "??1A@@QEAA@XZ"
 dec     r15
        jne     .LBB2_3
.LBB2_4:
        add     rbx, 8
 mov     rcx, rdi
        mov     rdx, rbx
        call "??_V@YAXPEAX_K@Z"
.LBB2_5:
        mov     qword ptr [rsi], 0
 mov     rcx, qword ptr [rsi + 8]
        test    rcx, rcx
        je .LBB2_9
        cmp     qword ptr [rcx - 8], 0
        je      .LBB2_7
 mov     rax, qword ptr [rcx]
        mov     edx, 3
        call qword ptr [rax]
        jmp     .LBB2_9
.LBB2_7:
        add     rcx, -8
 mov     edx, 8
        call    "??_V@YAXPEAX_K@Z"
 nop
.LBB2_9:
        add     rsp, 32
        pop     rbx
        pop rdi
        pop     rsi
        pop     r14
        pop     r15
 ret
```

The second `delete[]` in there is not right. What I observed in my own code (not this repro) was that it was not passing the original raw pointer returned from `new[]` to the `delete[]` operator, it was passing the address of the first element instead -- and this caused a crash.

But let's break down the above a bit. Here's the 2nd `delete[]` only:
```
.LBB2_5:
 mov     qword ptr [rsi], 0                    # null out first member (a = nullptr) after its delete[] completed
        mov     rcx, qword ptr [rsi + 8]              # rcx = b (element pointer returned by new B[n])
 test    rcx, rcx                              # b == nullptr ?
        je .LBB2_9                               # if null, skip deletion (ok)

 cmp     qword ptr [rcx - 8], 0                # load array cookie (n) from *(b - 8); compare n == 0
        je      .LBB2_7 # if n == 0, go free the allocation (cookie-only) path (ok)

 mov     rax, qword ptr [rcx]                  # rax = vptr of *b (first element)
        mov     edx, 3                                # MSVC deleting-dtor selector (scalar delete form; NOT array delete)
        call qword ptr [rax]                       # call virtual "deleting destructor" for ONE element only (wrong for delete[])
        jmp     .LBB2_9 # then skip any array deallocation (leaks / misses freeing whole block) (wrong)

.LBB2_7:
        add     rcx, -8 # rcx = cookie/raw pointer (base of allocation, not element pointer)
        mov     edx, 8                                # size = 8 bytes (cookie only; corresponds to n==0)
        call "??_V@YAXPEAX_K@Z"                    # call sized operator delete[](void*, size_t) with (base, 8)
        nop
.LBB2_9:
        ...
 ret
```

And here's what my production code has -- where we call the sized `delete[]` with the wrong address:
```
	xom::safe_delete_array(m_vbo);
00007FF7C82CF728  add rax,58h
00007FF7C82CF72C  mov         qword ptr [rsp+80h],rax
00007FF7C82CF734  mov         rax,qword ptr [rsp+80h]
00007FF7C82CF73C  mov         rax,qword ptr [rax]
00007FF7C82CF73F  mov         qword ptr [rsp+38h],rax
00007FF7C82CF744  cmp         rax,0
00007FF7C82CF748  je GLImmediate::~GLImmediate+12Ch (07FF7C82CF79Ch)

00007FF7C82CF74A  mov rax,qword ptr [rsp+38h]
00007FF7C82CF74F  mov         rcx,rax # rcx = element pointer (m_vbo), i.e. first element, not cookie/base
00007FF7C82CF752  add rcx,0FFFFFFFFFFFFFFF8h                       # rcx = (m_vbo - 8) => cookie/base pointer (raw allocation)
00007FF7C82CF756  mov         qword ptr [rsp+28h],rcx
00007FF7C82CF75B  mov         rax,qword ptr [rax-8] # rax = array cookie (n)
00007FF7C82CF75F  mov         qword ptr [rsp+30h],rax
00007FF7C82CF764  cmp         rax,0
00007FF7C82CF768  jne GLImmediate::~GLImmediate+113h (07FF7C82CF783h)

00007FF7C82CF76A  mov rcx,qword ptr [rsp+28h]                      # (n == 0) rcx = cookie/base pointer
00007FF7C82CF76F  mov         rax,qword ptr [rsp+30h]
00007FF7C82CF774  imul        rdx,rax,68h
00007FF7C82CF778  add rdx,8
00007FF7C82CF77C  call        operator delete[] (07FF7C8269920h) # (n == 0) delete[] called with cookie/base pointer (looks consistent)
00007FF7C82CF781  jmp         GLImmediate::~GLImmediate+12Ch (07FF7C82CF79Ch)

00007FF7C82CF783  mov         rcx,qword ptr [rsp+38h] # (n != 0) rcx = element pointer (m_vbo) again
00007FF7C82CF788  call        VertexBuffer::~VertexBuffer (07FF7C7FD22F0h) # (n != 0) destroys exactly ONE element (should be a loop for delete[])
00007FF7C82CF78D  mov         rcx,qword ptr [rsp+38h] # (n != 0) rcx still = element pointer, NOT cookie/base pointer
00007FF7C82CF792  mov         edx,68h # passes sizeof(VertexBuffer) as "size" (scalar-sized-delete shape)
00007FF7C82CF797  call        operator delete[] (07FF7C8269920h) # BUG: delete[] called with element pointer (m_vbo), not cookie/base (m_vbo-8)
00007FF7C82CF79C  mov         rcx,qword ptr [rsp+60h]
00007FF7C82CF7A1  mov         rax,qword ptr [rsp+80h]
00007FF7C82CF7A9  mov         qword ptr [rax],0
}
00007FF7C82CF7B0  call std::vector<GLImmediateVertex,std::allocator<GLImmediateVertex> >::~vector (07FF7C82D0D60h)
00007FF7C82CF7B5  nop
00007FF7C82CF7B6  add rsp,88h
00007FF7C82CF7BD  ret
```

_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to