https://github.com/tbaederr created 
https://github.com/llvm/llvm-project/pull/231131

Computing the offset of the entire cast path once doesn't work out in practice 
if the dynamic type of the expression we're casting doesn't match its static 
type. Do it one step at a time. This way we don't have to compute an offset at 
all, but we stil need to check the validity of the cast on every step.

>From cdfe0b679a78764c120edd0a3ea7718a8678bba9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Mon, 12 Oct 2026 13:23:13 +0200
Subject: [PATCH] [clang][bytecode] Do BaseToDerived casts step-by-step

---
 clang/lib/AST/ByteCode/Compiler.cpp | 31 ++++++++++-----
 clang/lib/AST/ByteCode/Interp.cpp   | 62 +++++++++++------------------
 clang/lib/AST/ByteCode/Interp.h     |  9 +----
 clang/lib/AST/ByteCode/Opcodes.td   |  4 +-
 clang/test/AST/ByteCode/records.cpp | 21 ++++++++++
 5 files changed, 71 insertions(+), 56 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index a78bf7acf054c..6ba5099a78b87 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -624,15 +624,28 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) {
   case CK_BaseToDerived: {
     if (!this->delegate(SubExpr))
       return false;
-    unsigned DerivedOffset =
-        collectBaseOffset(SubExpr->getType(), E->getType());
-
-    const Type *TargetType = E->getType().getTypePtr();
-    if (TargetType->isPointerOrReferenceType())
-      TargetType = TargetType->getPointeeType().getTypePtr();
-    return this->emitGetPtrDerivedPop(DerivedOffset,
-                                      /*NullOK=*/E->getType()->isPointerType(),
-                                      TargetType, E);
+
+    auto unwrapPointerType = [](const Type *T) -> const Type * {
+      if (T->isPointerOrReferenceType())
+        return T->getPointeeType().getTypePtr();
+      return T;
+    };
+
+    bool NullOK = false;
+    const Type *FinalType = E->getType().getTypePtr();
+    if (FinalType->isPointerOrReferenceType()) {
+      NullOK = true;
+      FinalType = FinalType->getPointeeType().getTypePtr();
+    }
+
+    for (const CXXBaseSpecifier *B :
+         llvm::drop_begin(llvm::reverse(E->path()))) {
+      const Type *TargetType = unwrapPointerType(B->getType().getTypePtr());
+      if (!this->emitGetPtrDerivedPop(NullOK, TargetType, FinalType, E))
+        return false;
+    }
+
+    return this->emitGetPtrDerivedPop(NullOK, FinalType, FinalType, E);
   }
 
   case CK_FloatingCast: {
diff --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index 21e5d3e617fe7..b217c14988f9c 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -637,33 +637,6 @@ bool CheckSubobject(InterpState &S, CodePtr OpPC, const 
Pointer &Ptr,
   return false;
 }
 
-bool CheckDowncast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
-                   uint32_t Offset) {
-  uint32_t MinOffset = Ptr.block()->getMetadataSize();
-  uint32_t PtrOffset = Ptr.getByteOffset();
-
-  // We subtract Offset from PtrOffset. The result must be at least
-  // MinOffset.
-  if (Offset < PtrOffset && (PtrOffset - Offset) >= MinOffset)
-    return true;
-
-  const auto *E = cast<CastExpr>(S.Current->getExpr(OpPC));
-  QualType ExprTy = E->getType();
-  if (ExprTy->isPointerOrReferenceType())
-    ExprTy = ExprTy->getPointeeType();
-
-  QualType TargetQT = ExprTy;
-  QualType MostDerivedQT = Ptr.getDeclPtr().getType();
-
-  if (MostDerivedQT->isPointerOrReferenceType())
-    MostDerivedQT = MostDerivedQT->getPointeeType();
-
-  S.CCEDiag(E, diag::note_constexpr_invalid_downcast)
-      << MostDerivedQT << TargetQT;
-
-  return false;
-}
-
 bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
   assert(Ptr.isLive() && "Pointer is not live");
   if (!Ptr.isConst())
@@ -1852,8 +1825,9 @@ bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t 
Off, bool NullOK) {
   return getBase(S, OpPC, Ptr.narrow(), Off, NullOK);
 }
 
-bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK,
-                      const Type *TargetType) {
+// Do ONE step of a BaseToDerived cast.
+bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, bool NullOK,
+                      const Type *TargetType, const Type *FinalType) {
   const Pointer &Ptr = S.Stk.pop<Pointer>().narrow();
   if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Derived))
     return false;
@@ -1873,21 +1847,31 @@ bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, 
uint32_t Off, bool NullOK,
 
   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()) {
+  auto diagFailedCast = [&]() -> bool {
     QualType MostDerivedType = Ptr.getDeclDesc()->getType();
     S.CCEDiag(S.Current->getSource(OpPC), 
diag::note_constexpr_invalid_downcast)
-        << MostDerivedType << QualType(TargetType, 0);
+        << MostDerivedType << QualType(FinalType, 0);
     return false;
-  }
+  };
+
+  // We're casting down one level, which isn't possible if the pointer is
+  // already the root.
+  if (Ptr.isRoot())
+    return diagFailedCast();
 
-  S.Stk.push<Pointer>(Ptr.atFieldSub(Off));
+  auto unwrapPointerType = [](QualType T) -> const Type * {
+    if (T->isPointerOrReferenceType())
+      return T->getPointeeType().getTypePtr();
+    return T.getTypePtr();
+  };
+
+  PtrView BasePtr = Ptr.view().getBase();
+  const Type *BaseTy = unwrapPointerType(BasePtr.getType());
+  if (!ASTContext::hasSameType(BaseTy, TargetType))
+    return diagFailedCast();
+
+  S.Stk.push<Pointer>(BasePtr);
   return true;
 }
 
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index b82600ca5db86..ea115b6fef7e3 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -64,11 +64,6 @@ bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer 
&Ptr,
 bool CheckSubobject(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
                     CheckSubobjectKind CSK);
 
-/// Checks if the dowcast using the given offset is possible with the given
-/// pointer.
-bool CheckDowncast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
-                   uint32_t Offset);
-
 /// Checks if a pointer points to const storage.
 bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
 
@@ -2105,8 +2100,8 @@ 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);
+bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, bool NullOK,
+                      const Type *TargetType, const Type *FinalType);
 
 inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) {
   if (S.checkingPotentialConstantExpression() && S.Current->isBottomFrame())
diff --git a/clang/lib/AST/ByteCode/Opcodes.td 
b/clang/lib/AST/ByteCode/Opcodes.td
index 447fec1f1d2f5..7cd2098514166 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -393,7 +393,9 @@ def FinishInitActivate : SuccessOpcode;
 def FinishInitActivatePop : SuccessOpcode;
 def FinishInitGlobal : SuccessOpcode;
 
-def GetPtrDerivedPop : Opcode { let Args = [ArgUint32, ArgBool, ArgTypePtr]; }
+def GetPtrDerivedPop : Opcode {
+  let Args = [ArgBool, ArgTypePtr, ArgTypePtr];
+}
 
 // [Pointer] -> [Pointer]
 def GetPtrVirtBasePop : Opcode {
diff --git a/clang/test/AST/ByteCode/records.cpp 
b/clang/test/AST/ByteCode/records.cpp
index ee47c8206f4ff..ab79384a10476 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -668,6 +668,27 @@ namespace C {
   static_assert(M4->a == nullptr);
   static_assert(M4->g2[0] == 0);
 }
+
+namespace D {
+  struct A {
+    char a;
+  };
+
+  template <typename T>
+  struct B {
+    constexpr T &bar() { return *static_cast<T *>(this); } // expected-note 
{{cannot cast object of dynamic type 'C<VirtualCalls::BaseToDerived::D::D>' to 
type 'VirtualCalls::BaseToDerived::D::D'}} \
+                                                           // ref-note 
{{cannot cast object of dynamic type 'C<D>' to type 
'VirtualCalls::BaseToDerived::D::D'}}
+  };
+
+  template <typename T>
+  struct C : A , B<T> {};
+
+  struct D : B<D> {};
+
+  constexpr auto L = C<D>().bar(); // both-error {{must be initialized by a 
constant expression}} \
+                                   // both-note {{in call to}}
+}
+
 }
 
 

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

Reply via email to