[clang] [clang] Avoid stack exhaustion in recursive `constexpr` calls (PR #201706)
Daniil Dudkin via cfe-commits
cfe-commits at lists.llvm.org
Sat Jun 6 10:49:41 PDT 2026
https://github.com/unterumarmung updated https://github.com/llvm/llvm-project/pull/201706
>From e4842b6b99edfe46e2a2047ccc20d9f274b4dbd4 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Wed, 3 Jun 2026 21:37:16 +0300
Subject: [PATCH 1/2] [clang] Avoid stack exhaustion in recursive constexpr
calls
Guard constexpr function-call evaluation with runWithSufficientStackSpace so deep recursive constexpr calls use a fresh stack before exhausting the current one.
Fixes #201418
---
clang/docs/ReleaseNotes.rst | 2 ++
clang/lib/AST/ExprConstant.cpp | 35 +++++++++++++++++----
clang/test/SemaCXX/constexpr-call-stack.cpp | 10 ++++++
3 files changed, 41 insertions(+), 6 deletions(-)
create mode 100644 clang/test/SemaCXX/constexpr-call-stack.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9e4a47a5b18fc..0f661fa1c1d73 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -716,6 +716,8 @@ Bug Fixes to C++ Support
- Fixed an invalid rejection and assertion failure while generating ``operator=`` for fields with the ``__restrict`` qualifier. (#GH37979)
- Fixed a use-after-free bug when parsing default arguments containing lambdas in declarations with template-id declarators. (#GH196725)
- Fixed a crash in constant evaluation using placement new on an array which was later initialized. (#GH196450)
+- Fixed a stack overflow crash when evaluating deeply recursive ``constexpr``
+ function calls. (#GH201418)
- Fixed an issue where Clang incorrectly accepted invalid unqualified uses of local nested class names outside their declaring scope. (#GH184622)
Bug Fixes to AST Handling
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 86c162cc040f9..f26340f9fac4f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -53,6 +53,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/StackExhaustionHandler.h"
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
@@ -816,6 +817,8 @@ namespace {
/// initialized after CurrentCall and CallStackDepth.
CallStackFrame BottomFrame;
+ StackExhaustionHandler StackHandler;
+
/// A stack of values whose lifetimes end at the end of some surrounding
/// evaluation frame.
llvm::SmallVector<Cleanup, 16> CleanupStack;
@@ -920,6 +923,7 @@ namespace {
BottomFrame(*this, SourceLocation(), /*Callee=*/nullptr,
/*This=*/nullptr,
/*CallExpr=*/nullptr, CallRef()),
+ StackHandler(C.getDiagnostics()),
EvaluatingDecl((const ValueDecl *)nullptr),
EvaluatingDeclValue(nullptr) {
EvalMode = Mode;
@@ -1009,6 +1013,13 @@ namespace {
return true;
}
+ bool runWithSufficientStackSpace(SourceLocation Loc,
+ llvm::function_ref<bool()> Fn) {
+ bool Result = false;
+ StackHandler.runWithSufficientStackSpace(Loc, [&] { Result = Fn(); });
+ return Result;
+ }
+
APValue *createHeapAlloc(const Expr *E, QualType T, LValue &LV);
std::optional<DynAlloc *> lookupDynamicAlloc(DynamicAllocLValue DA) {
@@ -7000,12 +7011,12 @@ static bool handleTrivialCopy(EvalInfo &Info, const ParmVarDecl *Param,
}
/// Evaluate a function call.
-static bool HandleFunctionCall(SourceLocation CallLoc,
- const FunctionDecl *Callee,
- const LValue *ObjectArg, const Expr *E,
- ArrayRef<const Expr *> Args, CallRef Call,
- const Stmt *Body, EvalInfo &Info,
- APValue &Result, const LValue *ResultSlot) {
+static bool HandleFunctionCallImpl(SourceLocation CallLoc,
+ const FunctionDecl *Callee,
+ const LValue *ObjectArg, const Expr *E,
+ ArrayRef<const Expr *> Args, CallRef Call,
+ const Stmt *Body, EvalInfo &Info,
+ APValue &Result, const LValue *ResultSlot) {
if (!Info.CheckCallLimit(CallLoc))
return false;
@@ -7059,6 +7070,18 @@ static bool HandleFunctionCall(SourceLocation CallLoc,
return ESR == ESR_Returned;
}
+static bool HandleFunctionCall(SourceLocation CallLoc,
+ const FunctionDecl *Callee,
+ const LValue *ObjectArg, const Expr *E,
+ ArrayRef<const Expr *> Args, CallRef Call,
+ const Stmt *Body, EvalInfo &Info,
+ APValue &Result, const LValue *ResultSlot) {
+ return Info.runWithSufficientStackSpace(CallLoc, [&] {
+ return HandleFunctionCallImpl(CallLoc, Callee, ObjectArg, E, Args, Call,
+ Body, Info, Result, ResultSlot);
+ });
+}
+
/// Evaluate a constructor call.
static bool HandleConstructorCall(const Expr *E, const LValue &This,
CallRef Call,
diff --git a/clang/test/SemaCXX/constexpr-call-stack.cpp b/clang/test/SemaCXX/constexpr-call-stack.cpp
new file mode 100644
index 0000000000000..6f0434987b753
--- /dev/null
+++ b/clang/test/SemaCXX/constexpr-call-stack.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -Wno-unused-value -Wno-stack-exhausted -fconstexpr-depth=1024 %s
+
+int rand();
+
+constexpr int a(int) {
+ {
+ (100000000001024 ^ a(0) * 0 ? 2147483647 : rand()) ? 2147483647 : 1;
+ }
+ return 0;
+}
>From b47123e84512130bb3e0281b9225d153dfe446cc Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Sat, 6 Jun 2026 20:49:19 +0300
Subject: [PATCH 2/2] add test for experimental-new-constant-interpreter
---
clang/test/SemaCXX/constexpr-call-stack.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/SemaCXX/constexpr-call-stack.cpp b/clang/test/SemaCXX/constexpr-call-stack.cpp
index 6f0434987b753..f224260aaec3d 100644
--- a/clang/test/SemaCXX/constexpr-call-stack.cpp
+++ b/clang/test/SemaCXX/constexpr-call-stack.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -Wno-unused-value -Wno-stack-exhausted -fconstexpr-depth=1024 %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -Wno-unused-value -Wno-stack-exhausted -fconstexpr-depth=1024 -fexperimental-new-constant-interpreter %s
int rand();
More information about the cfe-commits
mailing list