Skip to content

Conversation

mariusdr
Copy link
Contributor

Fixes #152893.

An assert was raised when a constexpr virtual function was called from an constexpr array element with -fexperimental-new-constant-interpreter set.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:bytecode Issues for the clang bytecode constexpr interpreter labels Sep 14, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 14, 2025

@llvm/pr-subscribers-clang

Author: marius doerner (mariusdr)

Changes

Fixes #152893.

An assert was raised when a constexpr virtual function was called from an constexpr array element with -fexperimental-new-constant-interpreter set.


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

2 Files Affected:

  • (modified) clang/lib/AST/ByteCode/Interp.cpp (+7-2)
  • (modified) clang/test/AST/ByteCode/cxx20.cpp (+22)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index f1b9104c04feb..c739fa0c19d84 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1664,10 +1664,15 @@ bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func,
       TypePtr = TypePtr.getBase();
 
     QualType DynamicType = TypePtr.getType();
-    if (DynamicType->isPointerType() || DynamicType->isReferenceType())
+    if (DynamicType->isPointerType() || DynamicType->isReferenceType()) {
       DynamicDecl = DynamicType->getPointeeCXXRecordDecl();
-    else
+    } else if (DynamicType->isArrayType()) {
+      const Type *ElemType = DynamicType->getPointeeOrArrayElementType();
+      assert(ElemType);
+      DynamicDecl = ElemType->getAsCXXRecordDecl();
+    } else {
       DynamicDecl = DynamicType->getAsCXXRecordDecl();
+    }
   }
   assert(DynamicDecl);
 
diff --git a/clang/test/AST/ByteCode/cxx20.cpp b/clang/test/AST/ByteCode/cxx20.cpp
index 67bf9a732d8b7..8e77a081e3e60 100644
--- a/clang/test/AST/ByteCode/cxx20.cpp
+++ b/clang/test/AST/ByteCode/cxx20.cpp
@@ -1100,3 +1100,25 @@ namespace DiscardedTrivialCXXConstructExpr {
   constexpr int y = foo(12); // both-error {{must be initialized by a constant expression}} \
                              // both-note {{in call to}}
 }
+
+namespace VirtualFunctionCallThroughArrayElem {
+  struct X {
+    constexpr virtual int foo() const {
+      return 3;
+    }
+  };
+  constexpr X xs[5];
+  static_assert(xs[3].foo() == 3);
+
+  constexpr X xs2[1][2];
+  static_assert(xs2[0].foo() == 3); // both-error {{is not a structure or union}}
+  static_assert(xs2[0][0].foo() == 3);
+
+  struct Y: public X {
+    constexpr int foo() const override {
+      return 1;
+    }
+  };
+  constexpr Y ys[20];
+  static_assert(ys[12].foo() == static_cast<const X&>(ys[12]).foo());
+}

@Fznamznon Fznamznon requested a review from tbaederr September 16, 2025 08:07
@tbaederr tbaederr changed the title [Clang][Interp] Assert on virtual func call from array elem [clang][bytecode] Assert on virtual func call from array elem Sep 16, 2025
return 1;
}
};
constexpr Y ys[20];
Copy link
Contributor

Choose a reason for hiding this comment

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

can you include the original reproducer from #152893 as well? This patch fixes the assertion failure but the diagnostic output is still different between the two interpreters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added that test case and the diagnostic. For normal globals the interpreters also disagreed in their diagnostics outputs (https://godbolt.org/z/84zeeYxd3), now they raise the same messages.

Fixes llvm#152893.

An assert was raised when a constexpr virtual function was called from
an constexpr array element with -fexperimental-new-constant-interpreter
set.
@mariusdr mariusdr force-pushed the interp-fix-virtual-fn-call-from-array branch from ab683e2 to 95717b9 Compare September 21, 2025 09:43
Copy link

github-actions bot commented Sep 21, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

DynamicDecl = DynamicType->getAsCXXRecordDecl();
}

if (!CheckConstant(S, OpPC, TypePtr, true)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So what is actually the relevant part of CheckConstant? Is it just the call to QualType::isConstant()?

Copy link
Contributor Author

@mariusdr mariusdr Sep 23, 2025

Choose a reason for hiding this comment

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

Checking only DynamicType.isConstant() would reject valid code like

  struct A {
    constexpr virtual void foo(int &a) {
      a = 1;
    }
  };

  constexpr int m() {
    A b;
    int a;
    b.foo(a);
    return a;
  }
  static_assert(m() == 1, "");

here TypePtr.isStatic() in GetDynamicDecl() is false so CheckConstant() would return early but I'm not sure if it is sufficient to only check those two things. Tests pass at least with if (TypePtr.isStatic() && !DynamicType.isConstant(S.getASTContext())) instead of CheckConstant().

Copy link
Contributor Author

@mariusdr mariusdr Oct 3, 2025

Choose a reason for hiding this comment

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

So what is actually the relevant part of CheckConstant? Is it just the call to QualType::isConstant()?

Removed the call to CheckConstant, added more test cases. Check should be more explicit now, i.e. check storage class of this, and if its var decl is constexpr.

if (!CheckConstant(S, OpPC, TypePtr, true)) {
const Expr *E = S.Current->getExpr(OpPC);
APValue V = TypePtr.toAPValue(S.getASTContext());
QualType TT = S.getASTContext().getLValueReferenceType(DynamicType);
Copy link
Contributor

Choose a reason for hiding this comment

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

Did Pointer::toDiagnosticString() not work here?

Copy link
Contributor Author

@mariusdr mariusdr Sep 23, 2025

Choose a reason for hiding this comment

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

The output is slightly different because toDiagnosticString() (~ APValue::getAsString(V, TypePtr.getType()) adds a '&' prefix to the output while the code above does not, because the input QualType is not a reference type, refer to:

if (!IsReference)
Out << '&';
else if (isLValueOnePastTheEnd())

we would get messages like

note: virtual function called on object '&k' whose dynamic type is not constant

while the other interpreter outputs

note: virtual function called on object 'k' whose dynamic type is not constant

Copy link
Contributor

@tbaederr tbaederr left a comment

Choose a reason for hiding this comment

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

okay, thanks!

@mariusdr
Copy link
Contributor Author

mariusdr commented Oct 6, 2025

Thanks for the review! Could you merge this for me (I can't commit). Thank you.

@tbaederr tbaederr merged commit 5296d01 into llvm:main Oct 6, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:bytecode Issues for the clang bytecode constexpr interpreter clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang][bytecode] Assertion `DynamicDecl' failed
3 participants