[clang] 1fee6a1 - [clang][bytecode] Ignore GetPtrDerivedPop on non-record pointers (#194005)

via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 27 02:23:48 PDT 2026


Author: Timm Baeder
Date: 2026-04-27T11:23:43+02:00
New Revision: 1fee6a1e04de55ec218f9f38cbf2f60fc43acd26

URL: https://github.com/llvm/llvm-project/commit/1fee6a1e04de55ec218f9f38cbf2f60fc43acd26
DIFF: https://github.com/llvm/llvm-project/commit/1fee6a1e04de55ec218f9f38cbf2f60fc43acd26.diff

LOG: [clang][bytecode] Ignore GetPtrDerivedPop on non-record pointers (#194005)

Now that we do this for `GetPtrBase`, we need to do it here, too. This
broke in the attached test case, but fully fixing it requires some
seemingly unrelated changes to `delete` handling.

Added: 
    

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

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 21a75b8fb4d45..729c0cc4ab541 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1300,7 +1300,7 @@ bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm,
 
     // Remove base casts.
     QualType InitialType = Ptr.getType();
-    Ptr = Ptr.stripBaseCasts();
+    Ptr = Ptr.expand().stripBaseCasts();
 
     Source = Ptr.getDeclDesc()->asExpr();
     BlockToDelete = Ptr.block();
@@ -1515,11 +1515,50 @@ static bool getBase(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
 
 bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off) {
   const auto &Ptr = S.Stk.peek<Pointer>();
-  return getBase(S, OpPC, Ptr, Off, /*NullOK=*/true);
+  return getBase(S, OpPC, Ptr.narrow(), Off, /*NullOK=*/true);
 }
 bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK) {
   const auto &Ptr = S.Stk.pop<Pointer>();
-  return getBase(S, OpPC, Ptr, Off, NullOK);
+  return getBase(S, OpPC, Ptr.narrow(), Off, NullOK);
+}
+
+bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK,
+                      const Type *TargetType) {
+  const Pointer &Ptr = S.Stk.pop<Pointer>().narrow();
+  if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Derived))
+    return false;
+
+  if (!Ptr.isBlockPointer()) {
+    // FIXME: We don't have the necessary information in integral pointers.
+    // The Descriptor only has a record, but that does of course not include
+    // the potential derived classes of said record.
+    S.Stk.push<Pointer>(Ptr);
+    return true;
+  }
+
+  if (!CheckSubobject(S, OpPC, Ptr, CSK_Derived))
+    return false;
+  if (!CheckDowncast(S, OpPC, Ptr, Off))
+    return false;
+
+  if (!Ptr.getFieldDesc()->isRecord()) {
+    S.Stk.push<Pointer>(Ptr);
+    return true;
+  }
+
+  const Record *TargetRecord = Ptr.atFieldSub(Off).getRecord();
+  assert(TargetRecord);
+
+  if (TargetRecord->getDecl()->getCanonicalDecl() !=
+      TargetType->getAsCXXRecordDecl()->getCanonicalDecl()) {
+    QualType MostDerivedType = Ptr.getDeclDesc()->getType();
+    S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_downcast)
+        << MostDerivedType << QualType(TargetType, 0);
+    return false;
+  }
+
+  S.Stk.push<Pointer>(Ptr.atFieldSub(Off));
+  return true;
 }
 
 static bool checkConstructor(InterpState &S, CodePtr OpPC, const Function *Func,

diff  --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index b9d69f5be3c00..412a9289e424e 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1985,6 +1985,9 @@ bool GetPtrFieldPop(InterpState &S, CodePtr OpPC, uint32_t Off);
 bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off);
 bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK);
 
+bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK,
+                      const Type *TargetType);
+
 inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) {
   if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0)
     return false;
@@ -1995,40 +1998,6 @@ inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) {
   return true;
 }
 
-inline bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off,
-                             bool NullOK, const Type *TargetType) {
-  const Pointer &Ptr = S.Stk.pop<Pointer>();
-  if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Derived))
-    return false;
-
-  if (!Ptr.isBlockPointer()) {
-    // FIXME: We don't have the necessary information in integral pointers.
-    // The Descriptor only has a record, but that does of course not include
-    // the potential derived classes of said record.
-    S.Stk.push<Pointer>(Ptr);
-    return true;
-  }
-
-  if (!CheckSubobject(S, OpPC, Ptr, CSK_Derived))
-    return false;
-  if (!CheckDowncast(S, OpPC, Ptr, Off))
-    return false;
-
-  const Record *TargetRecord = Ptr.atFieldSub(Off).getRecord();
-  assert(TargetRecord);
-
-  if (TargetRecord->getDecl()->getCanonicalDecl() !=
-      TargetType->getAsCXXRecordDecl()->getCanonicalDecl()) {
-    QualType MostDerivedType = Ptr.getDeclDesc()->getType();
-    S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_invalid_downcast)
-        << MostDerivedType << QualType(TargetType, 0);
-    return false;
-  }
-
-  S.Stk.push<Pointer>(Ptr.atFieldSub(Off));
-  return true;
-}
-
 inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) {
   if (S.checkingPotentialConstantExpression() && S.Current->isBottomFrame())
     return false;

diff  --git a/clang/test/AST/ByteCode/new-delete.cpp b/clang/test/AST/ByteCode/new-delete.cpp
index 6467aa2e1d20b..2a04f28032b6c 100644
--- a/clang/test/AST/ByteCode/new-delete.cpp
+++ b/clang/test/AST/ByteCode/new-delete.cpp
@@ -1154,6 +1154,40 @@ namespace BrokenDelete {
   }
 }
 
+namespace vdtor {
+  constexpr int vdtor_3(int mode) {
+    int a = 0;
+    struct S { constexpr virtual ~S() {} };
+    struct T : S {
+      constexpr T(int *p) : p(p) {}
+      constexpr ~T() { ++*p; }
+      int *p;
+    };
+    S *p = new T[3]{&a, &a, &a}; // both-note 2{{heap allocation}} \
+                                 // both-note {{allocated with 'new[]' here}}
+    switch (mode) {
+    case 0:
+      delete p; // both-note {{non-array delete used to delete pointer to array object of type 'T[3]'}} \
+                // both-warning {{'delete' applied to a pointer that was allocated with 'new[]'}}
+      break;
+    case 1:
+      delete[] p; // both-note {{delete of pointer to subobject '&{*new T[3]#0}[0]'}}
+      break;
+    case 2:
+      delete (T*)p; // both-note {{non-array delete used to delete pointer to array object of type 'T[3]'}}
+      break;
+    case 3:
+      delete[] (T*)p;
+      break;
+    }
+    return a;
+  }
+  static_assert(vdtor_3(0) == 3); // both-error {{}} both-note {{in call}}
+  static_assert(vdtor_3(1) == 1); // both-error {{}} both-note {{in call}}
+  static_assert(vdtor_3(2) == 3); // both-error {{}} both-note {{in call}}
+  static_assert(vdtor_3(3) == 3);
+}
+
 #else
 /// Make sure we reject this prior to C++20
 constexpr int a() { // both-error {{never produces a constant expression}}


        


More information about the cfe-commits mailing list