https://github.com/AZero13 updated 
https://github.com/llvm/llvm-project/pull/209593

>From dd4376ddb6bb3fea9d70b48b66b65ec98dc35470 Mon Sep 17 00:00:00 2001
From: AZero13 <[email protected]>
Date: Tue, 14 Jul 2026 14:57:42 -0400
Subject: [PATCH] [Clang] Restrict CWG1504 devirtualization to pointer
 arithmetic on record types

Fixes a regression introduced in PR #207540 where virtual calls (including 
destructor calls in delete expressions) were incorrectly devirtualized on 
pointers obtained from pointer arithmetic on pointers (e.g. array-of-pointers 
subscripts).
---
 clang/lib/AST/DeclCXX.cpp                     | 20 ++++++
 clang/lib/AST/Expr.cpp                        | 70 +++++++++++++++++++
 clang/test/CXX/drs/cwg15xx.cpp                | 19 +++++
 .../devirtualize-virtual-function-calls.cpp   | 58 ++++++++++++++-
 clang/www/cxx_dr_status.html                  |  2 +-
 5 files changed, 166 insertions(+), 3 deletions(-)

diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 0573cdf95952a..f1bc1a3283e5f 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -2600,6 +2600,26 @@ CXXMethodDecl 
*CXXMethodDecl::getDevirtualizedMethod(const Expr *Base,
     }
   }
 
+  // By CWG1504 / C++11 [expr.add]p6, pointer arithmetic on a base pointer into
+  // an array of derived objects is undefined behavior when the element type 
and
+  // pointee type are not similar. This means we can devirtualize calls on
+  // objects accessed through array subscripts or pointer arithmetic with
+  // non-zero offsets, since the dynamic type must match the static type.
+  // Expr::getBestDynamicClassTypeExpr evaluates this and will not strip
+  // the array subscript or pointer arithmetic if the offset is non-zero.
+  if (isa<ArraySubscriptExpr>(Base) ||
+      (isa<UnaryOperator>(Base) &&
+       cast<UnaryOperator>(Base)->getOpcode() == UO_Deref)) {
+    return DevirtualizedMethod;
+  }
+
+  if (const auto *BO = dyn_cast<BinaryOperator>(Base)) {
+    if (BO->getType()->isPointerType() &&
+        BO->getType()->getPointeeType()->isRecordType() &&
+        (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub))
+      return DevirtualizedMethod;
+  }
+
   // We can't devirtualize the call.
   return nullptr;
 }
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 0bb9c6aa01c39..eb2ad75f71efc 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -61,6 +61,76 @@ const Expr *Expr::getBestDynamicClassTypeExpr() const {
       continue;
     }
 
+    // CWG1504: Pointer arithmetic on an array of objects.
+    // If the index is zero or unknown, we cannot prove the dynamic type.
+    // So we strip the array subscript and evaluate the base.
+    // We restrict this check to dynamic classes (i.e., polymorphic C++ 
classes)
+    // to avoid severe performance regressions from EvaluateAsInt in C code
+    // and non-polymorphic C++ structs where devirtualization doesn't matter
+    // anyway.
+    if (auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
+      if (const CXXRecordDecl *CRD = ASE->getType()->getAsCXXRecordDecl()) {
+        if (CRD->isDynamicClass()) {
+          Expr::EvalResult Result;
+          if (!ASE->getIdx()->EvaluateAsInt(Result, CRD->getASTContext()) ||
+              Result.Val.getInt().isZero()) {
+            E = ASE->getBase();
+            continue;
+          }
+        }
+      }
+    }
+
+    // Same for pointer arithmetic with deref: *(p + N)
+    if (auto *UO = dyn_cast<UnaryOperator>(E)) {
+      if (UO->getOpcode() == UO_Deref) {
+        if (const CXXRecordDecl *CRD = UO->getType()->getAsCXXRecordDecl()) {
+          if (CRD->isDynamicClass()) {
+            const Expr *Inner = UO->getSubExpr()->IgnoreParenImpCasts();
+            if (auto *BO = dyn_cast<BinaryOperator>(Inner)) {
+              if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
+                bool PointerOnLHS = BO->getLHS()->getType()->isPointerType();
+                const Expr *IdxExpr =
+                    PointerOnLHS ? BO->getRHS() : BO->getLHS();
+                Expr::EvalResult Result;
+                if (!IdxExpr->EvaluateAsInt(Result, CRD->getASTContext()) ||
+                    Result.Val.getInt().isZero()) {
+                  E = UO->getSubExpr();
+                  continue;
+                }
+              } else {
+                E = UO->getSubExpr();
+                continue;
+              }
+            } else {
+              E = UO->getSubExpr();
+              continue;
+            }
+          }
+        }
+      }
+    }
+
+    // Same for pointer arithmetic with arrow: (p + N)->f()
+    if (auto *BO = dyn_cast<BinaryOperator>(E)) {
+      if ((BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) &&
+          BO->getType()->isPointerType()) {
+        if (const CXXRecordDecl *CRD =
+                BO->getType()->getPointeeType()->getAsCXXRecordDecl()) {
+          if (CRD->isDynamicClass()) {
+            bool PointerOnLHS = BO->getLHS()->getType()->isPointerType();
+            const Expr *IdxExpr = PointerOnLHS ? BO->getRHS() : BO->getLHS();
+            Expr::EvalResult Result;
+            if (!IdxExpr->EvaluateAsInt(Result, CRD->getASTContext()) ||
+                Result.Val.getInt().isZero()) {
+              E = PointerOnLHS ? BO->getLHS() : BO->getRHS();
+              continue;
+            }
+          }
+        }
+      }
+    }
+
     break;
   }
 
diff --git a/clang/test/CXX/drs/cwg15xx.cpp b/clang/test/CXX/drs/cwg15xx.cpp
index 5a9b80ed028c4..b9e0525772779 100644
--- a/clang/test/CXX/drs/cwg15xx.cpp
+++ b/clang/test/CXX/drs/cwg15xx.cpp
@@ -11,6 +11,25 @@
 // cxx98-error@-1 {{variadic macros are a C99 feature}}
 #endif
 
+namespace cwg1504 { // cwg1504: 3.1
+#if __cplusplus >= 201103L
+  // CWG1504: Pointer arithmetic after derived-base conversion
+  struct Base { int x; };
+  struct Derived : Base { int y; };
+  constexpr Derived arr[2] = {};
+
+  // Pointer arithmetic on a base pointer into a derived array is UB,
+  // and the constexpr evaluator must diagnose it.
+  constexpr int test(int n) {
+    return ((const Base*)arr)[n].x; // #cwg1504-x
+  }
+  constexpr int bad = test(1);
+  // since-cxx11-error@-1 {{constexpr variable 'bad' must be initialized by a 
constant expression}}
+  //   since-cxx11-note@#cwg1504-x {{cannot access field of pointer past the 
end of object}}
+  //   since-cxx11-note@-3 {{in call to 'test(1)'}}
+#endif
+} // namespace cwg1504
+
 namespace cwg1512 { // cwg1512: 4
   void f(char *p) {
     if (p > 0) {}
diff --git a/clang/test/CodeGenCXX/devirtualize-virtual-function-calls.cpp 
b/clang/test/CodeGenCXX/devirtualize-virtual-function-calls.cpp
index b50881db63e05..dac2c2b183d7d 100644
--- a/clang/test/CodeGenCXX/devirtualize-virtual-function-calls.cpp
+++ b/clang/test/CodeGenCXX/devirtualize-virtual-function-calls.cpp
@@ -92,10 +92,48 @@ void fd(D d, XD xd, D *p) {
   // CHECK: call void %
   p[0].f();
 
-  // FIXME: We can devirtualize this, by C++1z [expr.add]/6 (if the array
+  // We can devirtualize this, by CWG1504 / [expr.add]/6 (if the array
   // element type and the pointee type are not similar, behavior is undefined).
-  // CHECK: call void %
+  // CHECK: call void @_ZN1A1fEv
   p[1].f();
+
+  // Negative indices are also UB for the same reason.
+  // CHECK: call void @_ZN1A1fEv
+  p[-1].f();
+
+  // Pointer arithmetic with arrow syntax: (p + N)->f()
+  // CHECK: call void @_ZN1A1fEv
+  (p + 1)->f();
+
+  // Pointer subtraction with arrow syntax: (p - N)->f()
+  // CHECK: call void @_ZN1A1fEv
+  (p - 1)->f();
+
+  // Can't devirtualize with non-constant index; we can't prove N != 0.
+  int n = 1;
+  // CHECK: call void %
+  p[n].f();
+
+  // Zero through expression: p[1-1] evaluates to 0, can't devirtualize.
+  // CHECK: call void %
+  p[1-1].f();
+
+  // (p + 0)->f() also can't be devirtualized (same as *p).
+  // CHECK: call void %
+  (p + 0)->f();
+
+  // 1 + p is legal pointer arithmetic too.
+  // CHECK: call void @_ZN1A1fEv
+  (1 + p)->f();
+
+  // Constant variables are evaluated.
+  const int N = 1;
+  // CHECK: call void @_ZN1A1fEv
+  p[N].f();
+
+  // Pointer arithmetic with deref: *(p + 1)
+  // CHECK: call void @_ZN1A1fEv
+  (*(p + 1)).f();
 }
 
 struct B {
@@ -195,3 +233,19 @@ namespace test5 {
     q.f();
   }
 }
+
+namespace test6 {
+  struct Thing {
+    virtual ~Thing();
+    virtual void f();
+  };
+
+  // CHECK-LABEL: define {{.*}} @_ZN5test63fooEPPNS_5ThingE
+  void foo(Thing **instances) {
+    // CHECK: call void %
+    instances[1]->f();
+    
+    // CHECK: call void %
+    delete instances[1];
+  }
+}
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index af91ac559d274..ffa16dc066bfe 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -10309,7 +10309,7 @@ <h2 id="cxxdr">C++ defect report implementation 
status</h2>
     <td>[<a href="https://wg21.link/expr.add";>expr.add</a>]</td>
     <td>CD3</td>
     <td>Pointer arithmetic after derived-base conversion</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="full" align="center">Clang 3.1</td>
   </tr>
   <tr id="1505">
     <td><a 
href="https://cplusplus.github.io/CWG/issues/1505.html";>1505</a></td>

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

Reply via email to