Author: Timm Baeder Date: 2026-04-06T15:52:17+02:00 New Revision: 59e899e16b7492698bd7673a7cdbe3a54368b703
URL: https://github.com/llvm/llvm-project/commit/59e899e16b7492698bd7673a7cdbe3a54368b703 DIFF: https://github.com/llvm/llvm-project/commit/59e899e16b7492698bd7673a7cdbe3a54368b703.diff LOG: [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. Added: Modified: clang/lib/AST/ByteCode/Compiler.cpp clang/lib/AST/ByteCode/EvalEmitter.cpp clang/lib/AST/ByteCode/Interp.cpp clang/lib/AST/ByteCode/Interp.h clang/lib/AST/ByteCode/Opcodes.td clang/lib/AST/ByteCode/Pointer.h clang/test/AST/ByteCode/cxx26.cpp clang/test/SemaCXX/constant-expression-p2280r4.cpp Removed: ################################################################################ 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) { // diff erent 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..8cc3c9216f7f4 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -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); diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 2e7045b39c3db..91e9461befcd9 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}} \ _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
