[clang][bytecode] Don't unref constexpr-unknown references (#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.
This commit is contained in:
Timm Baeder 2026-04-06 15:52:17 +02:00 committed by GitHub
parent 2ccc941549
commit 59e899e16b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 127 additions and 23 deletions

View File

@ -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),

View File

@ -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())

View File

@ -259,14 +259,16 @@ 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 +816,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 +851,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 +2208,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);

View File

@ -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());

View File

@ -322,6 +322,9 @@ class OffsetOpcode : Opcode {
def GetPtrLocal : OffsetOpcode {
bit HasCustomEval = 1;
}
def GetRefLocal : OffsetOpcode {
bit HasCustomEval = 1;
}
// [] -> [Pointer]
def GetPtrParam : OffsetOpcode;
// [] -> [Pointer]

View File

@ -716,11 +716,21 @@ public:
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;

View File

@ -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;
}
}

View File

@ -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}} \