Author: cakgok
Date: 2026-05-06T10:03:27+02:00
New Revision: 7234297bf52c0acc4ac0798a50b566fc17041f15

URL: 
https://github.com/llvm/llvm-project/commit/7234297bf52c0acc4ac0798a50b566fc17041f15
DIFF: 
https://github.com/llvm/llvm-project/commit/7234297bf52c0acc4ac0798a50b566fc17041f15.diff

LOG: [clang][bytecode] Fix sized builtin operator delete handling (#195741)

**Problem:**

A crash happens with std::allocator triggered sized/aligned delete
operations with new constant evaluator.

`interp__builtin_operator_delete` currently consumes the top of the
interpreter stack as a `Pointer`.

This is correct for unsized delete:

```cpp
__builtin_operator_delete(p);
```

but not for sized/aligned delete reached through
`std::allocator<T>::deallocate`:

```cpp
__builtin_operator_delete(p, size);
__builtin_operator_delete(p, size, align);
```

In those cases, the trailing operands are pushed after the pointer, so
the stack top is the size/alignment operand rather than the pointer. The
delete builtin handler must discard those trailing operands first.

Tested versions: 22.1.3, Trunk (x86_64-pc-linux-gnu)

**How to reproduce:**
```cpp
#include <vector>
std::vector<int> v(1);
```

Without any additional headers:
```cpp
typedef __SIZE_TYPE__ size_t;

namespace std {
template <class T>
struct allocator {
  constexpr T *allocate(size_t n) {
    return static_cast<T *>(__builtin_operator_new(n * sizeof(T)));
  }

  constexpr void deallocate(T *p, size_t n) {
    __builtin_operator_delete(p, n * sizeof(T));
  }
};
}

constexpr bool f() {
  int *p = std::allocator<int>().allocate(1);
  std::allocator<int>().deallocate(p, 1);
  return true;
}

static_assert(f());
```

Crashes with:
```bash
clang++ -std=c++20 -fexperimental-new-constant-interpreter test.cpp
```

**Fix:**
Discard all trailing arguments before popping the main pointer argument.

**Testing:**
* Added a regression test.
* The regression goes through `std::allocator<T>::deallocate` rather
than calling `__builtin_operator_delete` directly. Because a direct user
call is caught before the deallocation logic that consumes the pointer
operand.

Assisted-by: gemini-cli

@tbaederr

---------

Co-authored-by: Timm Baeder <[email protected]>

Added: 
    

Modified: 
    clang/lib/AST/ByteCode/InterpBuiltin.cpp
    clang/test/AST/ByteCode/new-delete.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp 
b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 5bdd3a7824a17..11ca93c251380 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -1630,6 +1630,14 @@ static bool interp__builtin_operator_delete(InterpState 
&S, CodePtr OpPC,
   const Expr *Source = nullptr;
   const Block *BlockToDelete = nullptr;
 
+  unsigned NumArgs = Call->getNumArgs();
+  assert(NumArgs >= 1);
+
+  // Args are pushed in source order. The trailing sized/aligned delete
+  // operands are above the pointer on the stack.
+  for (unsigned I = NumArgs - 1; I != 0; --I)
+    discard(S.Stk, *S.getContext().classify(Call->getArg(I)));
+
   if (S.checkingPotentialConstantExpression()) {
     S.Stk.discard<Pointer>();
     return false;

diff  --git a/clang/test/AST/ByteCode/new-delete.cpp 
b/clang/test/AST/ByteCode/new-delete.cpp
index 4ade50b7c02e4..ac2c2ff4a73c6 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -643,6 +643,9 @@ namespace std {
                                     // both-note {{used to delete a null 
pointer}} \
                                     // both-note {{delete of pointer 
'&no_deallocate_nonalloc' that does not point to a heap-allocated object}}
     }
+    constexpr void deallocate(void *p, size_t N) {
+       __builtin_operator_delete(p, sizeof(T) * N);
+     }
   };
   template<typename T, typename ...Args>
   constexpr void construct_at(void *p, Args &&...args) { // #construct
@@ -767,6 +770,13 @@ namespace OperatorNewDelete {
                                                                                
         // both-note {{in call}}
 
   
static_assert((std::allocator<float>().deallocate(std::allocator<float>().allocate(10)),
 1) == 1);
+
+  constexpr bool sizedDeallocate() {
+    int *p = std::allocator<int>().allocate(1);
+    std::allocator<int>().deallocate(p, 1);
+    return true;
+  }
+  static_assert(sizedDeallocate());
 }
 
 namespace Limits {


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

Reply via email to