https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/209593
>From bc5cebd8159796cefc4696d83fee42a27ae0818e Mon Sep 17 00:00:00 2001 From: AZero13 <[email protected]> Date: Wed, 15 Jul 2026 12:36:01 -0400 Subject: [PATCH 1/2] [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 | 51 ++++++++++++++++ clang/test/CXX/drs/cwg15xx.cpp | 19 ++++++ .../devirtualize-virtual-function-calls.cpp | 58 ++++++++++++++++++- clang/www/cxx_dr_status.html | 2 +- 4 files changed, 127 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index f0da56542ae7e..db06d0bef2d3f 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -2602,6 +2602,57 @@ CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base, } } + // By CWG1504 / C++11 [expr.add]p6, accessing an object through non-zero + // pointer arithmetic (including array subscripting) 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 non-zero pointer arithmetic, including array + // subscripting, since the dynamic type must match the static type. + const Expr *Inner = Base->IgnoreParens(); + + // 1. Handle p[N].f() (dot syntax with array subscript). + // The expression must be an lvalue of record type. + if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Inner)) { + Expr::EvalResult Result; + if (ASE->getType()->isRecordType() && + ASE->getIdx()->EvaluateAsInt(Result, getASTContext()) && + !Result.Val.getInt().isZero()) + return DevirtualizedMethod; + } + + auto TryDevirtualizePointerArithmetic = + [&](const Expr *PtrExpr) -> CXXMethodDecl * { + if (const auto *BO = dyn_cast<BinaryOperator>(PtrExpr)) { + if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) { + if (const auto *PT = BO->getType()->getAs<PointerType>()) { + if (PT->getPointeeType()->isRecordType()) { + bool PointerOnLHS = BO->getLHS()->getType()->isPointerType(); + const Expr *IdxExpr = PointerOnLHS ? BO->getRHS() : BO->getLHS(); + Expr::EvalResult Result; + if (IdxExpr->EvaluateAsInt(Result, getASTContext()) && + !Result.Val.getInt().isZero()) + return DevirtualizedMethod; + } + } + } + } + return nullptr; + }; + + // 2. Handle (p + N)->f() (arrow syntax with pointer arithmetic). + if (CXXMethodDecl *MD = TryDevirtualizePointerArithmetic(Inner)) + return MD; + + // 3. Handle (*(p + N)).f() (pointer arithmetic with deref). + // Treat (*ptr).f() similarly to ptr->f() by examining the operand of a + // single dereference. + if (const auto *UO = dyn_cast<UnaryOperator>(Inner)) { + if (UO->getOpcode() == UO_Deref) + if (CXXMethodDecl *MD = TryDevirtualizePointerArithmetic( + UO->getSubExpr()->IgnoreParens())) + return MD; + } + // We can't devirtualize the call. return nullptr; } 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> >From b9fd091effa554ee16a217a8c07369f088eea332 Mon Sep 17 00:00:00 2001 From: AZero13 <[email protected]> Date: Fri, 17 Jul 2026 07:22:30 -0400 Subject: [PATCH 2/2] Update cwg15xx.cpp Co-authored-by: Vlad Serebrennikov <[email protected]> --- clang/test/CXX/drs/cwg15xx.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/CXX/drs/cwg15xx.cpp b/clang/test/CXX/drs/cwg15xx.cpp index b9e0525772779..9d95d12f53c6f 100644 --- a/clang/test/CXX/drs/cwg15xx.cpp +++ b/clang/test/CXX/drs/cwg15xx.cpp @@ -13,7 +13,6 @@ 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] = {}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
