[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:
parent
2ccc941549
commit
59e899e16b
@ -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),
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -322,6 +322,9 @@ class OffsetOpcode : Opcode {
|
||||
def GetPtrLocal : OffsetOpcode {
|
||||
bit HasCustomEval = 1;
|
||||
}
|
||||
def GetRefLocal : OffsetOpcode {
|
||||
bit HasCustomEval = 1;
|
||||
}
|
||||
// [] -> [Pointer]
|
||||
def GetPtrParam : OffsetOpcode;
|
||||
// [] -> [Pointer]
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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}} \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user