[clang] [clang][bytecode] Don't unref constexpr-unknown references (PR #190177)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 2 06:34:31 PDT 2026
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/190177
If the pointer for a reference is constexpr-unknown, use the pointer itself instead, instead of dereferencing it. Unfortunately, that means constexpr-unknown pointers to reach a lot more places than before.
>From 9673f34be905403ff23e7baf88754c16d4ac6bba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 2 Apr 2026 11:59:05 +0200
Subject: [PATCH] asef
---
clang/lib/AST/ByteCode/Compiler.cpp | 12 +++--
clang/lib/AST/ByteCode/EvalEmitter.cpp | 10 +++-
clang/lib/AST/ByteCode/Interp.cpp | 34 ++++++++++++--
clang/lib/AST/ByteCode/Interp.h | 46 ++++++++++++++++++-
clang/lib/AST/ByteCode/Opcodes.td | 3 ++
clang/lib/AST/ByteCode/Pointer.h | 10 ++++
clang/test/AST/ByteCode/cxx26.cpp | 22 ++++++++-
.../SemaCXX/constant-expression-p2280r4.cpp | 12 ++---
8 files changed, 127 insertions(+), 22 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 9d2457a2f35cd..4f517266336f2 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7491,8 +7491,10 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
// Local variables.
if (auto It = Locals.find(D); It != Locals.end()) {
const unsigned Offset = It->second.Offset;
- if (IsReference)
- return this->emitGetLocal(classifyPrim(E), Offset, E);
+ if (IsReference) {
+ assert(classifyPrim(E) == PT_Ptr);
+ return this->emitGetRefLocal(Offset, E);
+ }
return this->emitGetPtrLocal(Offset, E);
}
// Global variables.
@@ -7561,7 +7563,7 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
if (!Ctx.getLangOpts().CPlusPlus) {
if (VD->getAnyInitializer() && DeclType.isConstant(Ctx.getASTContext()) &&
!VD->isWeak())
- return revisit(VD, DeclType->isPointerType());
+ return revisit(VD, /*IsConstexprUnknown=*/false);
return this->emitDummyPtr(D, E);
}
@@ -7601,8 +7603,8 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
// different evaluation, so e.g. mutable reads don't work on it.
EvalIDScope _(Ctx);
return revisit(VD, IsConstexprUnknown);
- } else if (Ctx.getLangOpts().CPlusPlus26 && IsReference)
- return revisit(VD, true);
+ } else if (Ctx.getLangOpts().CPlusPlus23 && IsReference)
+ return revisit(VD, /*IsConstexprUnknown=*/true);
if (IsReference)
return this->emitInvalidDeclRef(cast<DeclRefExpr>(E),
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 9cbd786e883a2..036cd2b9a62fa 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -220,7 +220,7 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo Info) {
// Implicitly convert lvalue to rvalue, if requested.
if (ConvertResultToRValue) {
- if (!Ptr.isZero() && !Ptr.isDereferencable())
+ if (Ptr.isPastEnd())
return false;
if (Ptr.pointsToStringLiteral() && Ptr.isArrayRoot())
@@ -291,6 +291,14 @@ bool EvalEmitter::emitGetPtrLocal(uint32_t I, SourceInfo Info) {
return true;
}
+bool EvalEmitter::emitGetRefLocal(uint32_t I, SourceInfo Info) {
+ if (!isActive())
+ return true;
+
+ Block *B = getLocal(I);
+ return handleReference(S, OpPC, B);
+}
+
template <PrimType OpType>
bool EvalEmitter::emitGetLocal(uint32_t I, SourceInfo Info) {
if (!isActive())
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 96a8ad609eaf1..82efd1f550718 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -259,14 +259,17 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
S.Stk.discard<Pointer>();
}
+bool isConstexprUnknown(const Block *B) {
+ if (B->isDummy())
+ return isa_and_nonnull<ParmVarDecl>(B->getDescriptor()->asValueDecl());
+ return B->getDescriptor()->IsConstexprUnknown;
+}
+
bool isConstexprUnknown(const Pointer &P) {
if (!P.isBlockPointer())
return false;
- if (P.isDummy())
- return isa_and_nonnull<ParmVarDecl>(P.getDeclDesc()->asValueDecl());
-
- return P.getDeclDesc()->IsConstexprUnknown;
+ return isConstexprUnknown(P.block());
}
bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
@@ -814,7 +817,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return false;
if (!CheckVolatile(S, OpPC, Ptr, AK))
return false;
- if (!Ptr.isConst() && !S.inConstantContext() && isConstexprUnknown(Ptr))
+ if (isConstexprUnknown(Ptr))
return false;
return true;
}
@@ -849,6 +852,8 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
return false;
if (!CheckMutable(S, OpPC, Ptr))
return false;
+ if (Ptr.isConstexprUnknown())
+ return false;
return true;
}
@@ -2204,6 +2209,25 @@ bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
return false;
}
+bool handleReference(InterpState &S, CodePtr OpPC, Block *B) {
+ if (isConstexprUnknown(B)) {
+ S.Stk.push<Pointer>(B);
+ return true;
+ }
+
+ const auto &ID = B->getBlockDesc<const InlineDescriptor>();
+ if (!ID.IsInitialized) {
+ if (!S.checkingPotentialConstantExpression())
+ S.FFDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_use_uninit_reference);
+ return false;
+ }
+
+ assert(B->getDescriptor()->getPrimType() == PT_Ptr);
+ S.Stk.push<Pointer>(B->deref<Pointer>());
+ return true;
+}
+
bool GetTypeid(InterpState &S, CodePtr OpPC, const Type *TypePtr,
const Type *TypeInfoType) {
S.Stk.push<Pointer>(TypePtr, TypeInfoType);
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 2af697cfed389..b8ab8e2e52ec3 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -133,6 +133,7 @@ bool CheckDestructor(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
bool CheckFunctionDecl(InterpState &S, CodePtr OpPC, const FunctionDecl *FD);
bool CheckBitCast(InterpState &S, CodePtr OpPC, const Type *TargetType,
bool SrcIsVoidPtr);
+bool handleReference(InterpState &S, CodePtr OpPC, Block *B);
bool InvalidCast(InterpState &S, CodePtr OpPC, CastKind Kind, bool Fatal);
bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
@@ -140,6 +141,7 @@ bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I);
bool isConstexprUnknown(const Pointer &P);
+bool isConstexprUnknown(const Block *B);
enum class ShiftDir { Left, Right };
@@ -1637,6 +1639,9 @@ bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) {
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
+ if (!This.isDereferencable())
+ return false;
+
const Pointer &Field = This.atField(I);
assert(Field.canBeInitialized());
Field.deref<T>() = S.Stk.pop<T>();
@@ -1651,6 +1656,9 @@ bool InitThisFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) {
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
+ if (!This.isDereferencable())
+ return false;
+
const Pointer &Field = This.atField(I);
assert(Field.canBeInitialized());
Field.deref<T>() = S.Stk.pop<T>();
@@ -1670,6 +1678,9 @@ bool InitThisBitField(InterpState &S, CodePtr OpPC,
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
+ if (!This.isDereferencable())
+ return false;
+
const Pointer &Field = This.atField(FieldOffset);
assert(Field.canBeInitialized());
const auto &Value = S.Stk.pop<T>();
@@ -1686,6 +1697,9 @@ bool InitThisBitFieldActivate(InterpState &S, CodePtr OpPC,
if (!CheckThis(S, OpPC))
return false;
const Pointer &This = S.Current->getThis();
+ if (!This.isDereferencable())
+ return false;
+
const Pointer &Field = This.atField(FieldOffset);
assert(Field.canBeInitialized());
const auto &Value = S.Stk.pop<T>();
@@ -1702,6 +1716,9 @@ template <PrimType Name, class T = typename PrimConv<Name>::T>
bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
+ if (!Ptr.isDereferencable())
+ return false;
+
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckArray(S, OpPC, Ptr))
@@ -1717,6 +1734,9 @@ template <PrimType Name, class T = typename PrimConv<Name>::T>
bool InitFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
+ if (!Ptr.isDereferencable())
+ return false;
+
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckArray(S, OpPC, Ptr))
@@ -1734,6 +1754,9 @@ bool InitBitField(InterpState &S, CodePtr OpPC, uint32_t FieldOffset,
uint32_t FieldBitWidth) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
+ if (!Ptr.isDereferencable())
+ return false;
+
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckArray(S, OpPC, Ptr))
@@ -1764,6 +1787,9 @@ bool InitBitFieldActivate(InterpState &S, CodePtr OpPC, uint32_t FieldOffset,
uint32_t FieldBitWidth) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
+ if (!Ptr.isDereferencable())
+ return false;
+
if (!CheckRange(S, OpPC, Ptr, CSK_Field))
return false;
if (!CheckArray(S, OpPC, Ptr))
@@ -1799,6 +1825,11 @@ inline bool GetPtrLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
return true;
}
+inline bool GetRefLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
+ Block *LocalBlock = S.Current->getLocalBlock(I);
+ return handleReference(S, OpPC, LocalBlock);
+}
+
inline bool GetPtrParam(InterpState &S, CodePtr OpPC, uint32_t Index) {
if (S.Current->isBottomFrame())
return false;
@@ -1872,6 +1903,9 @@ inline bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off) {
return true;
}
+ if (isConstexprUnknown(Ptr))
+ return false;
+
if (!CheckSubobject(S, OpPC, Ptr, CSK_Base))
return false;
const Pointer &Result = Ptr.atField(Off);
@@ -1895,6 +1929,9 @@ inline bool GetPtrBasePop(InterpState &S, CodePtr OpPC, uint32_t Off,
return true;
}
+ if (isConstexprUnknown(Ptr))
+ return false;
+
if (!CheckSubobject(S, OpPC, Ptr, CSK_Base))
return false;
const Pointer &Result = Ptr.atField(Off);
@@ -2191,6 +2228,9 @@ bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
+ if (Ptr.isConstexprUnknown())
+ return false;
+
const Descriptor *Desc = Ptr.getFieldDesc();
if (Desc->isUnknownSizeArray())
return false;
@@ -2225,6 +2265,9 @@ bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>();
+ if (Ptr.isConstexprUnknown())
+ return false;
+
const Descriptor *Desc = Ptr.getFieldDesc();
if (Desc->isUnknownSizeArray())
return false;
@@ -2869,7 +2912,8 @@ inline bool This(InterpState &S, CodePtr OpPC) {
[[maybe_unused]] const Record *R = This.getRecord();
if (!R)
R = This.narrow().getRecord();
- assert(R);
+ if (!R)
+ return false;
assert(R->getDecl() ==
cast<CXXMethodDecl>(S.Current->getFunction()->getDecl())
->getParent());
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 5e4d0ab2a84af..0215cd92966ee 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -322,6 +322,9 @@ class OffsetOpcode : Opcode {
def GetPtrLocal : OffsetOpcode {
bit HasCustomEval = 1;
}
+def GetRefLocal : OffsetOpcode {
+ bit HasCustomEval = 1;
+}
// [] -> [Pointer]
def GetPtrParam : OffsetOpcode;
// [] -> [Pointer]
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index ea9c7d4cb04db..9205862d2f2dc 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -716,11 +716,21 @@ class Pointer {
return *reinterpret_cast<T *>(BS.Pointee->rawData() + ReadOffset);
}
+ bool isConstexprUnknown() const {
+ if (!isBlockPointer())
+ return false;
+ return getDeclDesc()->IsConstexprUnknown;
+ }
+
/// Whether this block can be read from at all. This is only true for
/// block pointers that point to a valid location inside that block.
bool isDereferencable() const {
if (!isBlockPointer())
return false;
+ if (isDummy())
+ return false;
+ if (isConstexprUnknown())
+ return false;
if (isPastEnd())
return false;
diff --git a/clang/test/AST/ByteCode/cxx26.cpp b/clang/test/AST/ByteCode/cxx26.cpp
index d4349d84996cc..769deb28cdf50 100644
--- a/clang/test/AST/ByteCode/cxx26.cpp
+++ b/clang/test/AST/ByteCode/cxx26.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=ref,both %s
-// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected,both %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Wunreachable-code -verify=ref,both %s
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Wunreachable-code -verify=expected,both %s -fexperimental-new-constant-interpreter
namespace std {
using size_t = decltype(sizeof(0));
@@ -73,3 +73,21 @@ namespace ConstexprUnknownNestedVariables {
static_assert(f() == 42);
}
+
+namespace ConstexprUnknownReference {
+
+ struct expected {
+ int val;
+ };
+
+ extern void __assert_fail();
+ bool test() {
+ expected e(5);
+
+ const int &x = e.val;
+ /// We used to get a warning for an always-true comparison.
+ &(static_cast<const int&>(x)) == &e.val ? void() : __assert_fail();
+ return true;
+ }
+
+}
diff --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index 7c8e3e975f091..f4a8fbcbbe427 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -211,16 +211,14 @@ namespace uninit_reference_used {
constexpr int &rr = (rr, y);
constexpr int &g() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return x;
}
constexpr int &gg = g(); // expected-error {{must be initialized by a constant expression}} \
// expected-note {{in call to 'g()'}}
constexpr int g2() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return x;
}
constexpr int gg2 = g2(); // expected-error {{must be initialized by a constant expression}} \
@@ -236,8 +234,7 @@ namespace uninit_reference_used {
typedef decltype(sizeof(1)) uintptr_t;
constexpr uintptr_t g4() {
uintptr_t * &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
*(uintptr_t*)x = 10;
return 3;
}
@@ -245,8 +242,7 @@ namespace uninit_reference_used {
// expected-note {{in call to 'g4()'}}
constexpr int g5() {
int &x = x; // expected-warning {{reference 'x' is not yet bound to a value when used within its own initialization}} \
- // nointerpreter-note {{use of reference outside its lifetime is not allowed in a constant expression}} \
- // interpreter-note {{read of uninitialized object is not allowed in a constant expression}}
+ // expected-note {{use of reference outside its lifetime is not allowed in a constant expression}}
return 3;
}
constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a constant expression}} \
More information about the cfe-commits
mailing list