Skip to content

Conversation

hvdijk
Copy link
Contributor

@hvdijk hvdijk commented Jul 24, 2025

CodeGenFunction::EmitCXXDeleteExpr contains logic to go from a pointer to an array to a pointer to the first element of the array using a getelementptr LLVM IR instruction. This was done for pointers that were not variable length arrays, as pointers to variable length arrays never existed in LLVM IR, but rather than checking for arrays that were not variable length arrays, it checked for arrays that had a constant bound. This caused incomplete arrays to be inadvertently omitted.

This getelementptr was necessary back when LLVM IR used typed pointers, but they have been gone for a while, a gep with a constant zero offset does nothing now, so we can simplify the code by removing that.

CodeGenFunction::EmitCXXDeleteExpr contains logic to go from a pointer
to an array to a pointer to the first element of the array using a
getelementptr LLVM IR instruction. This was done for pointers that were
not variable length arrays, as pointers to variable length arrays never
existed in LLVM IR, but rather than checking for arrays that were not
variable length arrays, it checked for arrays that had a constant bound.
This caused incomplete arrays to be inadvertently omitted.

This getelementptr was necessary back when LLVM IR used typed pointers,
but they have been gone for a while, a gep with a constant zero offset
does nothing now, so we can simplify the code by removing that.
@hvdijk hvdijk requested a review from efriedma-quic July 24, 2025 02:06
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jul 24, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 24, 2025

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Harald van Dijk (hvdijk)

Changes

CodeGenFunction::EmitCXXDeleteExpr contains logic to go from a pointer to an array to a pointer to the first element of the array using a getelementptr LLVM IR instruction. This was done for pointers that were not variable length arrays, as pointers to variable length arrays never existed in LLVM IR, but rather than checking for arrays that were not variable length arrays, it checked for arrays that had a constant bound. This caused incomplete arrays to be inadvertently omitted.

This getelementptr was necessary back when LLVM IR used typed pointers, but they have been gone for a while, a gep with a constant zero offset does nothing now, so we can simplify the code by removing that.


Full diff: https://github.com/llvm/llvm-project/pull/150359.diff

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+5-23)
  • (modified) clang/test/CodeGenCXX/delete.cpp (+25-7)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dd6b44dd0c657..af68fa099c917 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -819,6 +819,7 @@ Bug Fixes in This Version
   in different locations (#GH134404, #GH146976).
 - Fix a crash when marco name is empty in ``#pragma push_macro("")`` or
   ``#pragma pop_macro("")``. (GH149762).
+- Fix a crash when deleting a pointer to an incomplete array.
 
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 359e30cb8f5cd..888be7e468bb3 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -2146,30 +2146,12 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
     return;
   }
 
-  // We might be deleting a pointer to array.  If so, GEP down to the
-  // first non-array element.
-  // (this assumes that A(*)[3][7] is converted to [3 x [7 x %A]]*)
-  if (DeleteTy->isConstantArrayType()) {
-    llvm::Value *Zero = Builder.getInt32(0);
-    SmallVector<llvm::Value*,8> GEP;
-
-    GEP.push_back(Zero); // point at the outermost array
-
-    // For each layer of array type we're pointing at:
-    while (const ConstantArrayType *Arr
-             = getContext().getAsConstantArrayType(DeleteTy)) {
-      // 1. Unpeel the array type.
-      DeleteTy = Arr->getElementType();
-
-      // 2. GEP to the first element of the array.
-      GEP.push_back(Zero);
-    }
-
-    Ptr = Builder.CreateInBoundsGEP(Ptr, GEP, ConvertTypeForMem(DeleteTy),
-                                    Ptr.getAlignment(), "del.first");
+  // We might be deleting a pointer to array.
+  while (const ArrayType *Arr = getContext().getAsArrayType(DeleteTy)) {
+    // Unpeel the array type.
+    DeleteTy = Arr->getElementType();
   }
-
-  assert(ConvertTypeForMem(DeleteTy) == Ptr.getElementType());
+  Ptr = Ptr.withElementType(ConvertTypeForMem(DeleteTy));
 
   if (E->isArrayForm()) {
     EmitArrayDelete(*this, E, Ptr, DeleteTy);
diff --git a/clang/test/CodeGenCXX/delete.cpp b/clang/test/CodeGenCXX/delete.cpp
index d5b0dc6712910..21b9f8c2fcc2c 100644
--- a/clang/test/CodeGenCXX/delete.cpp
+++ b/clang/test/CodeGenCXX/delete.cpp
@@ -76,27 +76,45 @@ namespace test1 {
     ~A();
   };
 
-  // CHECK-LABEL: define{{.*}} void @_ZN5test14testEPA10_A20_NS_1AE(
-  void test(A (*arr)[10][20]) {
+  // CHECK-LABEL: define{{.*}} void @_ZN5test11fEPA10_A20_NS_1AE(
+  void f(A (*arr)[10][20]) {
     delete [] arr;
     // CHECK:      icmp eq ptr [[PTR:%.*]], null
     // CHECK-NEXT: br i1
 
-    // CHECK:      [[BEGIN:%.*]] = getelementptr inbounds [10 x [20 x [[A:%.*]]]], ptr [[PTR]], i32 0, i32 0, i32 0
-    // CHECK-NEXT: [[ALLOC:%.*]] = getelementptr inbounds i8, ptr [[BEGIN]], i64 -8
+    // CHECK:      [[ALLOC:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 -8
     // CHECK-NEXT: [[COUNT:%.*]] = load i64, ptr [[ALLOC]]
-    // CHECK:      [[END:%.*]] = getelementptr inbounds [[A]], ptr [[BEGIN]], i64 [[COUNT]]
-    // CHECK-NEXT: [[ISEMPTY:%.*]] = icmp eq ptr [[BEGIN]], [[END]]
+    // CHECK:      [[END:%.*]] = getelementptr inbounds [[A:%.*]], ptr [[PTR]], i64 [[COUNT]]
+    // CHECK-NEXT: [[ISEMPTY:%.*]] = icmp eq ptr [[PTR]], [[END]]
     // CHECK-NEXT: br i1 [[ISEMPTY]],
     // CHECK:      [[PAST:%.*]] = phi ptr [ [[END]], {{%.*}} ], [ [[CUR:%.*]], {{%.*}} ]
     // CHECK-NEXT: [[CUR:%.*]] = getelementptr inbounds [[A]], ptr [[PAST]], i64 -1
     // CHECK-NEXT: call void @_ZN5test11AD1Ev(ptr {{[^,]*}} [[CUR]])
-    // CHECK-NEXT: [[ISDONE:%.*]] = icmp eq ptr [[CUR]], [[BEGIN]]
+    // CHECK-NEXT: [[ISDONE:%.*]] = icmp eq ptr [[CUR]], [[PTR]]
     // CHECK-NEXT: br i1 [[ISDONE]]
     // CHECK:      [[MUL:%.*]] = mul i64 4, [[COUNT]]
     // CHECK-NEXT: [[SIZE:%.*]] = add i64 [[MUL]], 8
     // CHECK-NEXT: call void @_ZdaPvm(ptr noundef [[ALLOC]], i64 noundef [[SIZE]])
   }
+
+  // CHECK-LABEL: define{{.*}} void @_ZN5test11gEPA_NS_1AE(
+  void g(A (*arr)[]) {
+    delete [] arr;
+    // CHECK:      icmp eq ptr [[PTR:%.*]], null
+    // CHECK-NEXT: br i1
+
+    // CHECK:      [[ALLOC:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i64 -8
+    // CHECK-NEXT: [[COUNT:%.*]] = load i64, ptr [[ALLOC]]
+    // CHECK:      [[END:%.*]] = getelementptr inbounds [[A:%.*]], ptr [[PTR]], i64 [[COUNT]]
+    // CHECK-NEXT: [[ISEMPTY:%.*]] = icmp eq ptr [[PTR]], [[END]]
+    // CHECK-NEXT: br i1 [[ISEMPTY]],
+    // CHECK:      [[PAST:%.*]] = phi ptr [ [[END]], {{%.*}} ], [ [[CUR:%.*]], {{%.*}} ]
+    // CHECK-NEXT: [[CUR:%.*]] = getelementptr inbounds [[A]], ptr [[PAST]], i64 -1
+    // CHECK-NEXT: call void @_ZN5test11AD1Ev(ptr {{[^,]*}} [[CUR]])
+    // CHECK-NEXT: [[ISDONE:%.*]] = icmp eq ptr [[CUR]], [[PTR]]
+    // CHECK-NEXT: br i1 [[ISDONE]]
+    // CHECK:      call void @_ZdaPv(ptr noundef [[ALLOC]])
+  }
 }
 
 namespace test2 {

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hvdijk hvdijk merged commit 2228b4b into llvm:main Jul 24, 2025
10 checks passed
@hvdijk hvdijk deleted the fix-delete-ptr-to-incomplete-array branch July 24, 2025 17:43
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Jul 28, 2025
CodeGenFunction::EmitCXXDeleteExpr contains logic to go from a pointer
to an array to a pointer to the first element of the array using a
getelementptr LLVM IR instruction. This was done for pointers that were
not variable length arrays, as pointers to variable length arrays never
existed in LLVM IR, but rather than checking for arrays that were not
variable length arrays, it checked for arrays that had a constant bound.
This caused incomplete arrays to be inadvertently omitted.

This getelementptr was necessary back when LLVM IR used typed pointers,
but they have been gone for a while, a gep with a constant zero offset
does nothing now, so we can simplify the code by removing that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants