[clang] [clang-tools-extra] [clang][bytecode] Don't evaluate bound member function expressions in new constant interpreter (PR #194851)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 29 06:14:38 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangd
Author: cakgok (cakgok)
<details>
<summary>Changes</summary>
**Problem:**
A crash is triggered by clangd's hover feature when using C++23 and the new bytecode interpreter, which calls `Expr::EvaluateAsRValue()` to attempt constant folding on an expression under the cursor, even when it is not a valid constant expression.
Tested versions: 22.1.3, Trunk (x86_64-pc-linux-gnu)
**How to reproduce:**
```cpp
struct S { void f(); };
void g() { S s; s.f(); }
```
Running `clangd --check=repro.cpp`
(with `compile_flags.txt` containing `-std=c++23 -fexperimental-new-constant-interpreter`)
will crash.
`Assertion ItemTypes.back() == toPrimType<T>() failed.`
You can observe the same crash by hovering over STL iterators like `vec.begin()`.
**Relevant Stack Trace:**
```text
#<!-- -->8 clang::interp::InterpStack::pop<MemberPointer>()
#<!-- -->9 clang::interp::EvalEmitter::emitRet(PrimType, SourceInfo)
#<!-- -->10 clang::interp::Compiler<EvalEmitter>::visitExpr(Expr const*, bool)
#<!-- -->11 clang::interp::EvalEmitter::interpretExpr(Expr const*, bool, bool)
#<!-- -->12 clang::interp::Context::evaluateAsRValue(State&, Expr const*, APValue&)
#<!-- -->13 EvaluateAsRValue(EvalInfo&, Expr const*, APValue&)
#<!-- -->14 clang::Expr::EvaluateAsRValue(EvalResult&, ASTContext const&, bool) const
#<!-- -->15 clangd::(anon)::printExprValue(Expr const*, ASTContext const&)
#<!-- -->16 clangd::(anon)::printExprValue(SelectionTree::Node const*, ASTContext const&)
#<!-- -->17 clangd::getHover(...)
```
*Basically: `textDocument/hover` → `getHover` → `EvaluateAsRValue` → new constant interpreter → `MemberPointer` type mismatch on stack pop.*
When `Compiler<Emitter>::VisitMemberExpr()` encounters a non-static `CXXMethodDecl` member (a bound member function expression such as `s.f` in `s.f()`), it falls through to `visitDeclRef()`. This pushes a `FnPtr` onto the interpreter stack. However, the caller expects a `MemberPointer`, causing an assertion failure in `InterpStack::pop()`:
**Fix:**
* In `VisitMemberExpr()`, bail out early (`return false`) when the member is a non-static `CXXMethodDecl`, before reaching `visitDeclRef()`. This causes `EvaluateAsRValue()` to report failure gracefully rather than crashing. Bound member function expressions (`s.f`) are not valid constant expressions, so returning `false` should be semantically correct.
**Testing:**
* Added AST unit test (`EvaluateAsRValue.FailsGracefullyOnBoundMemberExpr`) that directly isolates a bound `MemberExpr` and passes it to `EvaluateAsRValue()`, asserting it returns `false` without crashing.
* Added clangd hover test (Hover.NoCrashOnBoundMemberFunctionWithNewInterpreter)
that reproduces the original crash scenario.
* *Note:* I could not add a Lit test because I believe this is unreachable via normal `clang` invocations. `Sema` strictly catches isolated bound member functions before constant evaluation. `clangd` has a unique path to triggering this.
**Root cause:**
This is exposed by C++23 specifically due to (I think P2280R4 / P2448R2):
- Relaxing the rules around "unknown" objects in constant evaluation, allowing `s` in `s.f()` to proceed past the base object check even though `s` is not constexpr and deferring failures to bytecode execution rather than rejecting them structurally.
@<!-- -->tbaederr
---
Full diff: https://github.com/llvm/llvm-project/pull/194851.diff
3 Files Affected:
- (modified) clang-tools-extra/clangd/unittests/HoverTests.cpp (+12)
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+8)
- (modified) clang/unittests/AST/EvaluateAsRValueTest.cpp (+51)
``````````diff
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 7b168b0bdca60..9ce244e05a024 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -5245,6 +5245,18 @@ TEST(Hover, FunctionParameters) {
}
}
+TEST(Hover, NoCrashOnBoundMemberFunctionWithNewInterpreter) {
+ Annotations Code(R"cpp(
+ struct S { void f(); };
+ void g() { S s; s.^f(); }
+ )cpp");
+ TestTU TU = TestTU::withCode(Code.code());
+ TU.ExtraArgs.push_back("-std=c++23");
+ TU.ExtraArgs.push_back("-fexperimental-new-constant-interpreter");
+ auto AST = TU.build();
+ getHover(AST, Code.point(), format::getLLVMStyle(), nullptr);
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index d4bbb8d3a8a3e..ca2a408c0eceb 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -2643,6 +2643,14 @@ bool Compiler<Emitter>::VisitMemberExpr(const MemberExpr *E) {
if (!this->discard(Base) && !this->emitSideEffect(E))
return false;
+ // Bound member functions (non-static CXXMethodDecls) cannot be
+ // constant-evaluated. visitDeclRef would blindly push a FnPtr,
+ // but the caller expects a MemberPointer, causing a stack mismatch.
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(Member)) {
+ if (!MD->isStatic())
+ return false;
+ }
+
return this->visitDeclRef(Member, E);
}
diff --git a/clang/unittests/AST/EvaluateAsRValueTest.cpp b/clang/unittests/AST/EvaluateAsRValueTest.cpp
index 1e17330863f26..d98c1d09d5bd4 100644
--- a/clang/unittests/AST/EvaluateAsRValueTest.cpp
+++ b/clang/unittests/AST/EvaluateAsRValueTest.cpp
@@ -154,3 +154,54 @@ TEST(EvaluateAsRValue, LValueToRValueConversionWorks) {
Args));
}
}
+
+class EvaluateBoundMemberFunctionVisitor
+ : public clang::DynamicRecursiveASTVisitor {
+public:
+ explicit EvaluateBoundMemberFunctionVisitor(clang::ASTContext &Ctx)
+ : Ctx(Ctx) {}
+
+ bool VisitMemberExpr(clang::MemberExpr *E) override {
+ if (llvm::isa<clang::CXXMethodDecl>(E->getMemberDecl())) {
+ clang::Expr::EvalResult Result;
+ bool EvalSucceeded = E->EvaluateAsRValue(Result, Ctx, true);
+ EXPECT_FALSE(EvalSucceeded);
+ }
+ return true;
+ }
+
+private:
+ clang::ASTContext &Ctx;
+};
+
+class EvaluateBoundMemberFunctionAction : public clang::ASTFrontendAction {
+public:
+ std::unique_ptr<clang::ASTConsumer>
+ CreateASTConsumer(clang::CompilerInstance &Compiler,
+ llvm::StringRef FilePath) override {
+ return std::make_unique<Consumer>();
+ }
+
+private:
+ class Consumer : public clang::ASTConsumer {
+ public:
+ ~Consumer() override {}
+ void HandleTranslationUnit(clang::ASTContext &Ctx) override {
+ EvaluateBoundMemberFunctionVisitor Evaluator(Ctx);
+ Evaluator.TraverseDecl(Ctx.getTranslationUnitDecl());
+ }
+ };
+};
+
+TEST(EvaluateAsRValue, FailsGracefullyOnBoundMemberExpr) {
+ std::string ModesToTest[] = {"", "-fexperimental-new-constant-interpreter"};
+ for (std::string const &Mode : ModesToTest) {
+ std::vector<std::string> Args(1, Mode);
+ Args.push_back("-std=c++23");
+ ASSERT_TRUE(runToolOnCodeWithArgs(
+ std::make_unique<EvaluateBoundMemberFunctionAction>(),
+ "struct S { void f(); };\n"
+ "void g() { S s; s.f(); }\n",
+ Args));
+ }
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/194851
More information about the cfe-commits
mailing list