https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/71315
>From b102c7d258e5538ad9f4a851191656243b913523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbae...@redhat.com> Date: Tue, 31 Oct 2023 14:57:51 +0100 Subject: [PATCH] EvaluationResult --- clang/lib/AST/CMakeLists.txt | 1 + clang/lib/AST/ExprConstant.cpp | 27 ++- clang/lib/AST/Interp/ByteCodeEmitter.cpp | 13 +- clang/lib/AST/Interp/ByteCodeEmitter.h | 5 +- clang/lib/AST/Interp/ByteCodeExprGen.cpp | 3 +- clang/lib/AST/Interp/ByteCodeExprGen.h | 12 +- clang/lib/AST/Interp/Context.cpp | 106 ++++++++---- clang/lib/AST/Interp/Context.h | 3 + clang/lib/AST/Interp/EvalEmitter.cpp | 153 +++++------------ clang/lib/AST/Interp/EvalEmitter.h | 11 +- clang/lib/AST/Interp/EvaluationResult.cpp | 196 ++++++++++++++++++++++ clang/lib/AST/Interp/EvaluationResult.h | 111 ++++++++++++ clang/lib/AST/Interp/Interp.cpp | 92 ---------- clang/lib/AST/Interp/Interp.h | 16 +- clang/lib/AST/Interp/Opcodes.td | 2 - clang/lib/AST/Interp/Pointer.cpp | 132 ++++++++++++--- clang/lib/AST/Interp/Pointer.h | 3 +- clang/test/AST/Interp/literals.cpp | 4 +- clang/test/AST/Interp/records.cpp | 1 - 19 files changed, 583 insertions(+), 308 deletions(-) create mode 100644 clang/lib/AST/Interp/EvaluationResult.cpp create mode 100644 clang/lib/AST/Interp/EvaluationResult.h diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index fe3f8c485ec1c56..ebcb3952198a5b5 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -75,6 +75,7 @@ add_clang_library(clangAST Interp/Function.cpp Interp/InterpBuiltin.cpp Interp/Floating.cpp + Interp/EvaluationResult.cpp Interp/Interp.cpp Interp/InterpBlock.cpp Interp/InterpFrame.cpp diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index b6b1e6617dffaa9..d6e223e77d6f1c3 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -15418,11 +15418,13 @@ static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) { if (Info.EnableNewConstInterp) { if (!Info.Ctx.getInterpContext().evaluateAsRValue(Info, E, Result)) return false; - } else { - if (!::Evaluate(Result, Info, E)) - return false; + return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result, + ConstantExprKind::Normal); } + if (!::Evaluate(Result, Info, E)) + return false; + // Implicit lvalue-to-rvalue cast. if (E->isGLValue()) { LValue LV; @@ -15650,6 +15652,13 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, EvalInfo Info(Ctx, Result, EM); Info.InConstantContext = true; + if (Info.EnableNewConstInterp) { + if (!Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val)) + return false; + return CheckConstantExpression(Info, getExprLoc(), + getStorageType(Ctx, this), Result.Val, Kind); + } + // The type of the object we're initializing is 'const T' for a class NTTP. QualType T = getType(); if (Kind == ConstantExprKind::ClassTemplateArgument) @@ -15662,10 +15671,10 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, APValue::LValueBase Base(&BaseMTE); Info.setEvaluatingDecl(Base, Result.Val); - LValue LVal; - LVal.set(Base); { + LValue LVal; + LVal.set(Base); // C++23 [intro.execution]/p5 // A full-expression is [...] a constant-expression // So we need to make sure temporary objects are destroyed after having @@ -15723,10 +15732,16 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, Info.setEvaluatingDecl(VD, Value); Info.InConstantContext = IsConstantInitialization; + SourceLocation DeclLoc = VD->getLocation(); + QualType DeclTy = VD->getType(); + if (Info.EnableNewConstInterp) { auto &InterpCtx = const_cast<ASTContext &>(Ctx).getInterpContext(); if (!InterpCtx.evaluateAsInitializer(Info, VD, Value)) return false; + + return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, + ConstantExprKind::Normal); } else { LValue LVal; LVal.set(VD); @@ -15744,8 +15759,6 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, llvm_unreachable("Unhandled cleanup; missing full expression marker?"); } - SourceLocation DeclLoc = VD->getLocation(); - QualType DeclTy = VD->getType(); return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, ConstantExprKind::Normal) && CheckMemoryLeaks(Info); diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.cpp b/clang/lib/AST/Interp/ByteCodeEmitter.cpp index c8abb7c17a38ba2..d3a9583bee59e59 100644 --- a/clang/lib/AST/Interp/ByteCodeEmitter.cpp +++ b/clang/lib/AST/Interp/ByteCodeEmitter.cpp @@ -19,8 +19,7 @@ using namespace clang; using namespace clang::interp; -Expected<Function *> -ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) { +Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) { // Set up argument indices. unsigned ParamOffset = 0; SmallVector<PrimType, 8> ParamTypes; @@ -108,10 +107,6 @@ ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) { // Compile the function body. if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) { - // Return a dummy function if compilation failed. - if (BailLocation) - return llvm::make_error<ByteCodeGenError>(*BailLocation); - Func->setIsFullyCompiled(true); return Func; } @@ -171,11 +166,7 @@ int32_t ByteCodeEmitter::getOffset(LabelTy Label) { return 0ull; } -bool ByteCodeEmitter::bail(const SourceLocation &Loc) { - if (!BailLocation) - BailLocation = Loc; - return false; -} +bool ByteCodeEmitter::bail(const SourceLocation &Loc) { return false; } /// Helper to write bytecode and bail out if 32-bit offsets become invalid. /// Pointers will be automatically marshalled as 32-bit IDs. diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.h b/clang/lib/AST/Interp/ByteCodeEmitter.h index 5520f8c3006106f..4ef36d784dc4d59 100644 --- a/clang/lib/AST/Interp/ByteCodeEmitter.h +++ b/clang/lib/AST/Interp/ByteCodeEmitter.h @@ -17,7 +17,6 @@ #include "PrimType.h" #include "Program.h" #include "Source.h" -#include "llvm/Support/Error.h" namespace clang { namespace interp { @@ -32,7 +31,7 @@ class ByteCodeEmitter { public: /// Compiles the function into the module. - llvm::Expected<Function *> compileFunc(const FunctionDecl *FuncDecl); + Function *compileFunc(const FunctionDecl *FuncDecl); protected: ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {} @@ -81,8 +80,6 @@ class ByteCodeEmitter { LabelTy NextLabel = 0; /// Offset of the next local variable. unsigned NextLocalOffset = 0; - /// Location of a failure. - std::optional<SourceLocation> BailLocation; /// Label information for linker. llvm::DenseMap<LabelTy, unsigned> LabelOffsets; /// Location of label relocations. diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 485893d58f487ae..a27293566c26368 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -2143,7 +2143,8 @@ bool ByteCodeExprGen<Emitter>::visitDecl(const VarDecl *VD) { return this->emitRet(*VarT, VD); } - return this->emitRetValue(VD); + // Return non-primitive values as pointers here. + return this->emitRet(PT_Ptr, VD); } template <class Emitter> diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h index 83986d3dd579ed6..4d9cd84b714247e 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -129,7 +129,13 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>, /// Classifies a type. std::optional<PrimType> classify(const Expr *E) const { - return E->isGLValue() ? PT_Ptr : classify(E->getType()); + if (E->isGLValue()) { + if (E->getType()->isFunctionType()) + return PT_FnPtr; + return PT_Ptr; + } + + return classify(E->getType()); } std::optional<PrimType> classify(QualType Ty) const { return Ctx.classify(Ty); @@ -184,10 +190,6 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>, if (!visitInitializer(Init)) return false; - if ((Init->getType()->isArrayType() || Init->getType()->isRecordType()) && - !this->emitCheckGlobalCtor(Init)) - return false; - return this->emitPopPtr(Init); } diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp index cb96e56fb5e1ad8..b1b0a5d101b3767 100644 --- a/clang/lib/AST/Interp/Context.cpp +++ b/clang/lib/AST/Interp/Context.cpp @@ -30,18 +30,8 @@ Context::~Context() {} bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) { assert(Stk.empty()); Function *Func = P->getFunction(FD); - if (!Func || !Func->hasBody()) { - if (auto R = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD)) { - Func = *R; - } else { - handleAllErrors(R.takeError(), [&Parent](ByteCodeGenError &Err) { - Parent.FFDiag(Err.getRange().getBegin(), - diag::err_experimental_clang_interp_failed) - << Err.getRange(); - }); - return false; - } - } + if (!Func || !Func->hasBody()) + Func = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD); APValue DummyResult; if (!Run(Parent, Func, DummyResult)) { @@ -54,36 +44,90 @@ bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) { bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) { assert(Stk.empty()); ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result); - if (Check(Parent, C.interpretExpr(E))) { - assert(Stk.empty()); -#ifndef NDEBUG - // Make sure we don't rely on some value being still alive in - // InterpStack memory. + + auto Res = C.interpretExpr(E); + + if (Res.isInvalid()) { Stk.clear(); + return false; + } + + assert(Stk.empty()); +#ifndef NDEBUG + // Make sure we don't rely on some value being still alive in + // InterpStack memory. + Stk.clear(); #endif - return true; + + // Implicit lvalue-to-rvalue conversion. + if (E->isGLValue()) { + std::optional<APValue> RValueResult = Res.toRValue(); + if (!RValueResult) { + return false; + } + Result = *RValueResult; + } else { + Result = Res.toAPValue(); } + return true; +} + +bool Context::evaluate(State &Parent, const Expr *E, APValue &Result) { + assert(Stk.empty()); + ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result); + + auto Res = C.interpretExpr(E); + if (Res.isInvalid()) { + Stk.clear(); + return false; + } + + assert(Stk.empty()); +#ifndef NDEBUG + // Make sure we don't rely on some value being still alive in + // InterpStack memory. Stk.clear(); - return false; +#endif + Result = Res.toAPValue(); + return true; } bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result) { assert(Stk.empty()); ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result); - if (Check(Parent, C.interpretDecl(VD))) { - assert(Stk.empty()); -#ifndef NDEBUG - // Make sure we don't rely on some value being still alive in - // InterpStack memory. + + auto Res = C.interpretDecl(VD); + if (Res.isInvalid()) { Stk.clear(); -#endif - return true; + return false; } + assert(Stk.empty()); +#ifndef NDEBUG + // Make sure we don't rely on some value being still alive in + // InterpStack memory. Stk.clear(); - return false; +#endif + + // Ensure global variables are fully initialized. + if (shouldBeGloballyIndexed(VD) && !Res.isInvalid() && + (VD->getType()->isRecordType() || VD->getType()->isArrayType())) { + assert(Res.isLValue()); + + if (!Res.checkFullyInitialized(C.getState())) + return false; + + // lvalue-to-rvalue conversion. + std::optional<APValue> RValueResult = Res.toRValue(); + if (!RValueResult) + return false; + Result = *RValueResult; + + } else + Result = Res.toAPValue(); + return true; } const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); } @@ -231,12 +275,8 @@ const Function *Context::getOrCreateFunction(const FunctionDecl *FD) { return Func; if (!Func || WasNotDefined) { - if (auto R = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD)) - Func = *R; - else { - llvm::consumeError(R.takeError()); - return nullptr; - } + if (auto F = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD)) + Func = F; } return Func; diff --git a/clang/lib/AST/Interp/Context.h b/clang/lib/AST/Interp/Context.h index 7649caef2242816..ab83a8d13224670 100644 --- a/clang/lib/AST/Interp/Context.h +++ b/clang/lib/AST/Interp/Context.h @@ -51,6 +51,9 @@ class Context final { /// Evaluates a toplevel expression as an rvalue. bool evaluateAsRValue(State &Parent, const Expr *E, APValue &Result); + /// Like evaluateAsRvalue(), but does no implicit lvalue-to-rvalue conversion. + bool evaluate(State &Parent, const Expr *E, APValue &Result); + /// Evaluates a toplevel initializer. bool evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result); diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp index 9bc42057c5f5782..458fd7a21754672 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -19,7 +19,7 @@ using namespace clang::interp; EvalEmitter::EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk, APValue &Result) - : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), Result(Result) { + : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), EvalResult(&Ctx) { // Create a dummy frame for the interpreter which does not have locals. S.Current = new InterpFrame(S, /*Func=*/nullptr, /*Caller=*/nullptr, CodePtr()); @@ -33,20 +33,22 @@ EvalEmitter::~EvalEmitter() { } } -llvm::Expected<bool> EvalEmitter::interpretExpr(const Expr *E) { - if (this->visitExpr(E)) - return true; - if (BailLocation) - return llvm::make_error<ByteCodeGenError>(*BailLocation); - return false; +EvaluationResult EvalEmitter::interpretExpr(const Expr *E) { + EvalResult.setSource(E); + + if (!this->visitExpr(E)) + EvalResult.setInvalid(); + + return std::move(this->EvalResult); } -llvm::Expected<bool> EvalEmitter::interpretDecl(const VarDecl *VD) { - if (this->visitDecl(VD)) - return true; - if (BailLocation) - return llvm::make_error<ByteCodeGenError>(*BailLocation); - return false; +EvaluationResult EvalEmitter::interpretDecl(const VarDecl *VD) { + EvalResult.setSource(VD); + + if (!this->visitDecl(VD)) + EvalResult.setInvalid(); + + return std::move(this->EvalResult); } void EvalEmitter::emitLabel(LabelTy Label) { @@ -77,11 +79,7 @@ Scope::Local EvalEmitter::createLocal(Descriptor *D) { return {Off, D}; } -bool EvalEmitter::bail(const SourceLocation &Loc) { - if (!BailLocation) - BailLocation = Loc; - return false; -} +bool EvalEmitter::bail(const SourceLocation &Loc) { return false; } bool EvalEmitter::jumpTrue(const LabelTy &Label) { if (isActive()) { @@ -116,104 +114,37 @@ template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) { if (!isActive()) return true; using T = typename PrimConv<OpType>::T; - return ReturnValue<T>(S.Stk.pop<T>(), Result); + EvalResult.setValue(S.Stk.pop<T>().toAPValue()); + return true; +} + +template <> bool EvalEmitter::emitRet<PT_Ptr>(const SourceInfo &Info) { + if (!isActive()) + return true; + EvalResult.setPointer(S.Stk.pop<Pointer>()); + return true; +} +template <> bool EvalEmitter::emitRet<PT_FnPtr>(const SourceInfo &Info) { + if (!isActive()) + return true; + EvalResult.setFunctionPointer(S.Stk.pop<FunctionPointer>()); + return true; } -bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { return true; } +bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { + EvalResult.setValid(); + return true; +} bool EvalEmitter::emitRetValue(const SourceInfo &Info) { - // Method to recursively traverse composites. - std::function<bool(QualType, const Pointer &, APValue &)> Composite; - Composite = [this, &Composite](QualType Ty, const Pointer &Ptr, APValue &R) { - if (const auto *AT = Ty->getAs<AtomicType>()) - Ty = AT->getValueType(); - - if (const auto *RT = Ty->getAs<RecordType>()) { - const auto *Record = Ptr.getRecord(); - assert(Record && "Missing record descriptor"); - - bool Ok = true; - if (RT->getDecl()->isUnion()) { - const FieldDecl *ActiveField = nullptr; - APValue Value; - for (const auto &F : Record->fields()) { - const Pointer &FP = Ptr.atField(F.Offset); - QualType FieldTy = F.Decl->getType(); - if (FP.isActive()) { - if (std::optional<PrimType> T = Ctx.classify(FieldTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue<T>(FP.deref<T>(), Value)); - } else { - Ok &= Composite(FieldTy, FP, Value); - } - break; - } - } - R = APValue(ActiveField, Value); - } else { - unsigned NF = Record->getNumFields(); - unsigned NB = Record->getNumBases(); - unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases(); - - R = APValue(APValue::UninitStruct(), NB, NF); - - for (unsigned I = 0; I < NF; ++I) { - const Record::Field *FD = Record->getField(I); - QualType FieldTy = FD->Decl->getType(); - const Pointer &FP = Ptr.atField(FD->Offset); - APValue &Value = R.getStructField(I); - - if (std::optional<PrimType> T = Ctx.classify(FieldTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue<T>(FP.deref<T>(), Value)); - } else { - Ok &= Composite(FieldTy, FP, Value); - } - } - - for (unsigned I = 0; I < NB; ++I) { - const Record::Base *BD = Record->getBase(I); - QualType BaseTy = Ctx.getASTContext().getRecordType(BD->Decl); - const Pointer &BP = Ptr.atField(BD->Offset); - Ok &= Composite(BaseTy, BP, R.getStructBase(I)); - } - - for (unsigned I = 0; I < NV; ++I) { - const Record::Base *VD = Record->getVirtualBase(I); - QualType VirtBaseTy = Ctx.getASTContext().getRecordType(VD->Decl); - const Pointer &VP = Ptr.atField(VD->Offset); - Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I)); - } - } - return Ok; - } - - if (Ty->isIncompleteArrayType()) { - R = APValue(APValue::UninitArray(), 0, 0); - return true; - } - - if (const auto *AT = Ty->getAsArrayTypeUnsafe()) { - const size_t NumElems = Ptr.getNumElems(); - QualType ElemTy = AT->getElementType(); - R = APValue(APValue::UninitArray{}, NumElems, NumElems); - - bool Ok = true; - for (unsigned I = 0; I < NumElems; ++I) { - APValue &Slot = R.getArrayInitializedElt(I); - const Pointer &EP = Ptr.atIndex(I); - if (std::optional<PrimType> T = Ctx.classify(ElemTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue<T>(EP.deref<T>(), Slot)); - } else { - Ok &= Composite(ElemTy, EP.narrow(), Slot); - } - } - return Ok; - } - llvm_unreachable("invalid value to return"); - }; - - // Return the composite type. const auto &Ptr = S.Stk.pop<Pointer>(); - return Composite(Ptr.getType(), Ptr, Result); + if (std::optional<APValue> APV = Ptr.toRValue(S.getCtx())) { + EvalResult.setValue(*APV); + return true; + } + + EvalResult.setInvalid(); + return false; } bool EvalEmitter::emitGetPtrLocal(uint32_t I, const SourceInfo &Info) { diff --git a/clang/lib/AST/Interp/EvalEmitter.h b/clang/lib/AST/Interp/EvalEmitter.h index 5a9be18c34a03bc..e2516056ff5d2ca 100644 --- a/clang/lib/AST/Interp/EvalEmitter.h +++ b/clang/lib/AST/Interp/EvalEmitter.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_AST_INTERP_EVALEMITTER_H #define LLVM_CLANG_AST_INTERP_EVALEMITTER_H +#include "EvaluationResult.h" #include "InterpState.h" #include "PrimType.h" #include "Source.h" @@ -33,8 +34,10 @@ class EvalEmitter : public SourceMapper { using AddrTy = uintptr_t; using Local = Scope::Local; - llvm::Expected<bool> interpretExpr(const Expr *E); - llvm::Expected<bool> interpretDecl(const VarDecl *VD); + EvaluationResult interpretExpr(const Expr *E); + EvaluationResult interpretDecl(const VarDecl *VD); + + InterpState &getState() { return S; } protected: EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk, @@ -86,7 +89,7 @@ class EvalEmitter : public SourceMapper { /// Callee evaluation state. InterpState S; /// Location to write the result to. - APValue &Result; + EvaluationResult EvalResult; /// Temporaries which require storage. llvm::DenseMap<unsigned, std::unique_ptr<char[]>> Locals; @@ -100,8 +103,6 @@ class EvalEmitter : public SourceMapper { // The emitter always tracks the current instruction and sets OpPC to a token // value which is mapped to the location of the opcode being evaluated. CodePtr OpPC; - /// Location of a failure. - std::optional<SourceLocation> BailLocation; /// Location of the current instruction. SourceInfo CurrentSource; diff --git a/clang/lib/AST/Interp/EvaluationResult.cpp b/clang/lib/AST/Interp/EvaluationResult.cpp new file mode 100644 index 000000000000000..a14dc87f1dfde69 --- /dev/null +++ b/clang/lib/AST/Interp/EvaluationResult.cpp @@ -0,0 +1,196 @@ +//===----- EvaluationResult.cpp - Result class for the VM ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "EvaluationResult.h" +#include "Context.h" +#include "InterpState.h" +#include "Record.h" +#include "clang/AST/ExprCXX.h" + +namespace clang { +namespace interp { + +APValue EvaluationResult::toAPValue() const { + assert(!empty()); + switch (Kind) { + case LValue: + // Either a pointer or a function pointer. + if (const auto *P = std::get_if<Pointer>(&Value)) + return P->toAPValue(); + else if (const auto *FP = std::get_if<FunctionPointer>(&Value)) + return FP->toAPValue(); + else + llvm_unreachable("Unhandled LValue type"); + break; + case RValue: + return std::get<APValue>(Value); + case Valid: + return APValue(); + default: + llvm_unreachable("Unhandled result kind?"); + } +} + +std::optional<APValue> EvaluationResult::toRValue() const { + if (Kind == RValue) + return toAPValue(); + + assert(Kind == LValue); + + // We have a pointer and want an RValue. + if (const auto *P = std::get_if<Pointer>(&Value)) + return P->toRValue(*Ctx); + else if (const auto *FP = std::get_if<FunctionPointer>(&Value)) // Nope + return FP->toAPValue(); + llvm_unreachable("Unhandled lvalue kind"); +} + +static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc, + const FieldDecl *SubObjDecl) { + assert(SubObjDecl && "Subobject declaration does not exist"); + S.FFDiag(Loc, diag::note_constexpr_uninitialized) + << /*(name)*/ 1 << SubObjDecl; + S.Note(SubObjDecl->getLocation(), + diag::note_constexpr_subobject_declared_here); +} + +static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, + const Pointer &BasePtr, const Record *R); + +static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc, + const Pointer &BasePtr, + const ConstantArrayType *CAT) { + bool Result = true; + size_t NumElems = CAT->getSize().getZExtValue(); + QualType ElemType = CAT->getElementType(); + + if (ElemType->isRecordType()) { + const Record *R = BasePtr.getElemRecord(); + for (size_t I = 0; I != NumElems; ++I) { + Pointer ElemPtr = BasePtr.atIndex(I).narrow(); + Result &= CheckFieldsInitialized(S, Loc, ElemPtr, R); + } + } else if (const auto *ElemCAT = dyn_cast<ConstantArrayType>(ElemType)) { + for (size_t I = 0; I != NumElems; ++I) { + Pointer ElemPtr = BasePtr.atIndex(I).narrow(); + Result &= CheckArrayInitialized(S, Loc, ElemPtr, ElemCAT); + } + } else { + for (size_t I = 0; I != NumElems; ++I) { + if (!BasePtr.atIndex(I).isInitialized()) { + DiagnoseUninitializedSubobject(S, Loc, BasePtr.getField()); + Result = false; + } + } + } + + return Result; +} + +static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, + const Pointer &BasePtr, const Record *R) { + assert(R); + bool Result = true; + // Check all fields of this record are initialized. + for (const Record::Field &F : R->fields()) { + Pointer FieldPtr = BasePtr.atField(F.Offset); + QualType FieldType = F.Decl->getType(); + + if (FieldType->isRecordType()) { + Result &= CheckFieldsInitialized(S, Loc, FieldPtr, FieldPtr.getRecord()); + } else if (FieldType->isIncompleteArrayType()) { + // Nothing to do here. + } else if (FieldType->isArrayType()) { + const auto *CAT = + cast<ConstantArrayType>(FieldType->getAsArrayTypeUnsafe()); + Result &= CheckArrayInitialized(S, Loc, FieldPtr, CAT); + } else if (!FieldPtr.isInitialized()) { + DiagnoseUninitializedSubobject(S, Loc, F.Decl); + Result = false; + } + } + + // Check Fields in all bases + for (const Record::Base &B : R->bases()) { + Pointer P = BasePtr.atField(B.Offset); + if (!P.isInitialized()) { + S.FFDiag(BasePtr.getDeclDesc()->asDecl()->getLocation(), + diag::note_constexpr_uninitialized_base) + << B.Desc->getType(); + return false; + } + Result &= CheckFieldsInitialized(S, Loc, P, B.R); + } + + // TODO: Virtual bases + + return Result; +} + +bool EvaluationResult::checkFullyInitialized(InterpState &S) const { + assert(Source); + assert(isLValue()); + + // Our Source must be a VarDecl. + const Decl *SourceDecl = Source.dyn_cast<const Decl *>(); + assert(SourceDecl); + const auto *VD = cast<VarDecl>(SourceDecl); + assert(VD->getType()->isRecordType() || VD->getType()->isArrayType()); + SourceLocation InitLoc = VD->getAnyInitializer()->getExprLoc(); + + const Pointer &Ptr = *std::get_if<Pointer>(&Value); + assert(!Ptr.isZero()); + + if (const Record *R = Ptr.getRecord()) + return CheckFieldsInitialized(S, InitLoc, Ptr, R); + const auto *CAT = + cast<ConstantArrayType>(Ptr.getType()->getAsArrayTypeUnsafe()); + return CheckArrayInitialized(S, InitLoc, Ptr, CAT); + + return true; +} + +void EvaluationResult::dump() const { + assert(Ctx); + auto &OS = llvm::errs(); + const ASTContext &ASTCtx = Ctx->getASTContext(); + + switch (Kind) { + case Empty: + OS << "Empty\n"; + break; + case RValue: + OS << "RValue: "; + std::get<APValue>(Value).dump(OS, ASTCtx); + break; + case LValue: { + assert(Source); + QualType SourceType; + if (const auto *D = Source.dyn_cast<const Decl *>()) { + if (const auto *VD = dyn_cast<ValueDecl>(D)) + SourceType = VD->getType(); + } else if (const auto *E = Source.dyn_cast<const Expr *>()) { + SourceType = E->getType(); + } + + OS << "LValue: "; + if (const auto *P = std::get_if<Pointer>(&Value)) + P->toAPValue().printPretty(OS, ASTCtx, SourceType); + else if (const auto *FP = std::get_if<FunctionPointer>(&Value)) // Nope + FP->toAPValue().printPretty(OS, ASTCtx, SourceType); + OS << "\n"; + break; + } + + default: + llvm_unreachable("Can't print that."); + } +} + +} // namespace interp +} // namespace clang diff --git a/clang/lib/AST/Interp/EvaluationResult.h b/clang/lib/AST/Interp/EvaluationResult.h new file mode 100644 index 000000000000000..2b9fc16f1a0abc7 --- /dev/null +++ b/clang/lib/AST/Interp/EvaluationResult.h @@ -0,0 +1,111 @@ +//===------ EvaluationResult.h - Result class for the VM -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_EVALUATION_RESULT_H +#define LLVM_CLANG_AST_INTERP_EVALUATION_RESULT_H + +#include "FunctionPointer.h" +#include "Pointer.h" +#include "clang/AST/APValue.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include <optional> +#include <variant> + +namespace clang { +namespace interp { +class EvalEmitter; +class Context; + +/// Defines the result of an evaluation. +/// +/// The result might be in different forms--one of the pointer types, +/// an APValue, or nothing. +/// +/// We use this class to inspect and diagnose the result, as well as +/// convert it to the requested form. +class EvaluationResult final { +public: + enum ResultKind { + Empty, // Initial state. + LValue, // Result is an lvalue/pointer. + RValue, // Result is an rvalue. + Invalid, // Result is invalid. + Valid, // Result is valid and empty. + }; + + using DeclTy = llvm::PointerUnion<const Decl *, const Expr *>; + +private: + const Context *Ctx = nullptr; + std::variant<std::monostate, Pointer, FunctionPointer, APValue> Value; + ResultKind Kind = Empty; + DeclTy Source = nullptr; // Currently only needed for dump(). + + EvaluationResult(ResultKind Kind) : Kind(Kind) { + // Leave everything empty. Can be used as an + // error marker or for void return values. + assert(Kind == Valid || Kind == Invalid); + } + + void setSource(DeclTy D) { Source = D; } + + void setValue(const APValue &V) { + assert(empty()); + assert(!V.isLValue()); + Value = std::move(V); + Kind = RValue; + } + void setPointer(const Pointer P) { + assert(empty()); + Value = P; + Kind = LValue; + } + void setFunctionPointer(const FunctionPointer &P) { + assert(empty()); + Value = P; + Kind = LValue; + } + void setInvalid() { + assert(empty()); + Kind = Invalid; + } + void setValid() { + assert(empty()); + Kind = Valid; + } + +public: + EvaluationResult(const Context *Ctx) : Ctx(Ctx) {} + + bool empty() const { return Kind == Empty; } + bool isInvalid() const { return Kind == Invalid; } + bool isLValue() const { return Kind == LValue; } + bool isRValue() const { return Kind == RValue; } + + /// Returns an APValue for the evaluation result. The returned + /// APValue might be an LValue or RValue. + APValue toAPValue() const; + + /// If the result is an LValue, convert that to an RValue + /// and return it. This may fail, e.g. if the result is an + /// LValue and we can't read from it. + std::optional<APValue> toRValue() const; + + bool checkFullyInitialized(InterpState &S) const; + + /// Dump to stderr. + void dump() const; + + friend class EvalEmitter; +}; + +} // namespace interp +} // namespace clang + +#endif diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index 144b674451e353c..7cc02e278fd6a08 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -436,98 +436,6 @@ bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD) { return false; } -static void DiagnoseUninitializedSubobject(InterpState &S, const SourceInfo &SI, - const FieldDecl *SubObjDecl) { - assert(SubObjDecl && "Subobject declaration does not exist"); - S.FFDiag(SI, diag::note_constexpr_uninitialized) - << /*(name)*/ 1 << SubObjDecl; - S.Note(SubObjDecl->getLocation(), - diag::note_constexpr_subobject_declared_here); -} - -static bool CheckFieldsInitialized(InterpState &S, CodePtr OpPC, - const Pointer &BasePtr, const Record *R); - -static bool CheckArrayInitialized(InterpState &S, CodePtr OpPC, - const Pointer &BasePtr, - const ConstantArrayType *CAT) { - bool Result = true; - size_t NumElems = CAT->getSize().getZExtValue(); - QualType ElemType = CAT->getElementType(); - - if (ElemType->isRecordType()) { - const Record *R = BasePtr.getElemRecord(); - for (size_t I = 0; I != NumElems; ++I) { - Pointer ElemPtr = BasePtr.atIndex(I).narrow(); - Result &= CheckFieldsInitialized(S, OpPC, ElemPtr, R); - } - } else if (const auto *ElemCAT = dyn_cast<ConstantArrayType>(ElemType)) { - for (size_t I = 0; I != NumElems; ++I) { - Pointer ElemPtr = BasePtr.atIndex(I).narrow(); - Result &= CheckArrayInitialized(S, OpPC, ElemPtr, ElemCAT); - } - } else { - for (size_t I = 0; I != NumElems; ++I) { - if (!BasePtr.atIndex(I).isInitialized()) { - DiagnoseUninitializedSubobject(S, S.Current->getSource(OpPC), - BasePtr.getField()); - Result = false; - } - } - } - - return Result; -} - -static bool CheckFieldsInitialized(InterpState &S, CodePtr OpPC, - const Pointer &BasePtr, const Record *R) { - assert(R); - bool Result = true; - // Check all fields of this record are initialized. - for (const Record::Field &F : R->fields()) { - Pointer FieldPtr = BasePtr.atField(F.Offset); - QualType FieldType = F.Decl->getType(); - - if (FieldType->isRecordType()) { - Result &= CheckFieldsInitialized(S, OpPC, FieldPtr, FieldPtr.getRecord()); - } else if (FieldType->isIncompleteArrayType()) { - // Nothing to do here. - } else if (FieldType->isArrayType()) { - const auto *CAT = - cast<ConstantArrayType>(FieldType->getAsArrayTypeUnsafe()); - Result &= CheckArrayInitialized(S, OpPC, FieldPtr, CAT); - } else if (!FieldPtr.isInitialized()) { - DiagnoseUninitializedSubobject(S, S.Current->getSource(OpPC), F.Decl); - Result = false; - } - } - - // Check Fields in all bases - for (const Record::Base &B : R->bases()) { - Pointer P = BasePtr.atField(B.Offset); - if (!P.isInitialized()) { - S.FFDiag(BasePtr.getDeclDesc()->asDecl()->getLocation(), - diag::note_constexpr_uninitialized_base) - << B.Desc->getType(); - return false; - } - Result &= CheckFieldsInitialized(S, OpPC, P, B.R); - } - - // TODO: Virtual bases - - return Result; -} - -bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This) { - assert(!This.isZero()); - if (const Record *R = This.getRecord()) - return CheckFieldsInitialized(S, OpPC, This, R); - const auto *CAT = - cast<ConstantArrayType>(This.getType()->getAsArrayTypeUnsafe()); - return CheckArrayInitialized(S, OpPC, This, CAT); -} - bool CheckPotentialReinterpretCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { if (!S.inConstantContext()) diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 7dd415d6e460536..4b7bf9f42cbc5c0 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -109,9 +109,6 @@ bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); /// Checks if a method is pure virtual. bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD); -/// Checks that all fields are initialized after a constructor call. -bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This); - /// Checks if reinterpret casts are legal in the current context. bool CheckPotentialReinterpretCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr); @@ -1048,8 +1045,12 @@ inline bool InitGlobalTempComp(InterpState &S, CodePtr OpPC, const Pointer &P = S.Stk.peek<Pointer>(); APValue *Cached = Temp->getOrCreateValue(true); - *Cached = P.toRValue(S.getCtx()); - return true; + if (std::optional<APValue> APV = P.toRValue(S.getCtx())) { + *Cached = *APV; + return true; + } + + return false; } template <PrimType Name, class T = typename PrimConv<Name>::T> @@ -1847,11 +1848,6 @@ inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) { return NarrowPtr(S, OpPC); } -inline bool CheckGlobalCtor(InterpState &S, CodePtr OpPC) { - const Pointer &Obj = S.Stk.peek<Pointer>(); - return CheckCtorCall(S, OpPC, Obj); -} - inline bool Call(InterpState &S, CodePtr OpPC, const Function *Func) { if (Func->hasThisPointer()) { size_t ThisOffset = diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index 69068e87d5720ab..df01d5a29160192 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -375,8 +375,6 @@ def GetLocal : AccessOpcode { let HasCustomEval = 1; } // [] -> [Pointer] def SetLocal : AccessOpcode { let HasCustomEval = 1; } -def CheckGlobalCtor : Opcode {} - // [] -> [Value] def GetGlobal : AccessOpcode; // [Value] -> [] diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp index e979b99b0fdd0a0..0fcac6c0fe69c8b 100644 --- a/clang/lib/AST/Interp/Pointer.cpp +++ b/clang/lib/AST/Interp/Pointer.cpp @@ -231,33 +231,121 @@ bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) { return hasSameBase(A, B) && A.Base == B.Base && A.getFieldDesc()->IsArray; } -APValue Pointer::toRValue(const Context &Ctx) const { - // Primitives. - if (getFieldDesc()->isPrimitive()) { - PrimType PT = *Ctx.classify(getType()); - TYPE_SWITCH(PT, return deref<T>().toAPValue()); - llvm_unreachable("Unhandled PrimType?"); - } +std::optional<APValue> Pointer::toRValue(const Context &Ctx) const { + // Method to recursively traverse composites. + std::function<bool(QualType, const Pointer &, APValue &)> Composite; + Composite = [&Composite, &Ctx](QualType Ty, const Pointer &Ptr, APValue &R) { + if (const auto *AT = Ty->getAs<AtomicType>()) + Ty = AT->getValueType(); + + // Invalid pointers. + if (Ptr.isDummy() || !Ptr.isLive() || + (!Ptr.isUnknownSizeArray() && Ptr.isOnePastEnd())) + return false; - APValue Result; - // Records. - if (getFieldDesc()->isRecord()) { - const Record *R = getRecord(); - Result = - APValue(APValue::UninitStruct(), R->getNumBases(), R->getNumFields()); - - for (unsigned I = 0; I != R->getNumFields(); ++I) { - const Pointer &FieldPtr = this->atField(R->getField(I)->Offset); - Result.getStructField(I) = FieldPtr.toRValue(Ctx); + // Primitive values. + if (std::optional<PrimType> T = Ctx.classify(Ty)) { + if (T == PT_Ptr || T == PT_FnPtr) { + R = Ptr.toAPValue(); + } else { + TYPE_SWITCH(*T, R = Ptr.deref<T>().toAPValue()); + } + return true; } - for (unsigned I = 0; I != R->getNumBases(); ++I) { - const Pointer &BasePtr = this->atField(R->getBase(I)->Offset); - Result.getStructBase(I) = BasePtr.toRValue(Ctx); + if (const auto *RT = Ty->getAs<RecordType>()) { + const auto *Record = Ptr.getRecord(); + assert(Record && "Missing record descriptor"); + + bool Ok = true; + if (RT->getDecl()->isUnion()) { + const FieldDecl *ActiveField = nullptr; + APValue Value; + for (const auto &F : Record->fields()) { + const Pointer &FP = Ptr.atField(F.Offset); + QualType FieldTy = F.Decl->getType(); + if (FP.isActive()) { + if (std::optional<PrimType> T = Ctx.classify(FieldTy)) { + TYPE_SWITCH(*T, Value = FP.deref<T>().toAPValue()); + } else { + Ok &= Composite(FieldTy, FP, Value); + } + break; + } + } + R = APValue(ActiveField, Value); + } else { + unsigned NF = Record->getNumFields(); + unsigned NB = Record->getNumBases(); + unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases(); + + R = APValue(APValue::UninitStruct(), NB, NF); + + for (unsigned I = 0; I < NF; ++I) { + const Record::Field *FD = Record->getField(I); + QualType FieldTy = FD->Decl->getType(); + const Pointer &FP = Ptr.atField(FD->Offset); + APValue &Value = R.getStructField(I); + + if (std::optional<PrimType> T = Ctx.classify(FieldTy)) { + TYPE_SWITCH(*T, Value = FP.deref<T>().toAPValue()); + } else { + Ok &= Composite(FieldTy, FP, Value); + } + } + + for (unsigned I = 0; I < NB; ++I) { + const Record::Base *BD = Record->getBase(I); + QualType BaseTy = Ctx.getASTContext().getRecordType(BD->Decl); + const Pointer &BP = Ptr.atField(BD->Offset); + Ok &= Composite(BaseTy, BP, R.getStructBase(I)); + } + + for (unsigned I = 0; I < NV; ++I) { + const Record::Base *VD = Record->getVirtualBase(I); + QualType VirtBaseTy = Ctx.getASTContext().getRecordType(VD->Decl); + const Pointer &VP = Ptr.atField(VD->Offset); + Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I)); + } + } + return Ok; } - } - // TODO: Arrays + if (Ty->isIncompleteArrayType()) { + R = APValue(APValue::UninitArray(), 0, 0); + return true; + } + + if (const auto *AT = Ty->getAsArrayTypeUnsafe()) { + const size_t NumElems = Ptr.getNumElems(); + QualType ElemTy = AT->getElementType(); + R = APValue(APValue::UninitArray{}, NumElems, NumElems); + + bool Ok = true; + for (unsigned I = 0; I < NumElems; ++I) { + APValue &Slot = R.getArrayInitializedElt(I); + const Pointer &EP = Ptr.atIndex(I); + if (std::optional<PrimType> T = Ctx.classify(ElemTy)) { + TYPE_SWITCH(*T, Slot = EP.deref<T>().toAPValue()); + } else { + Ok &= Composite(ElemTy, EP.narrow(), Slot); + } + } + return Ok; + } + llvm_unreachable("invalid value to return"); + }; + + if (isZero()) + return APValue(static_cast<Expr *>(nullptr), CharUnits::Zero(), {}, false, + true); + + if (isDummy() || !isLive()) + return std::nullopt; + // Return the composite type. + APValue Result; + if (!Composite(getType(), *this, Result)) + return std::nullopt; return Result; } diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h index 843bcad16b5d1e3..0fef15774302d10 100644 --- a/clang/lib/AST/Interp/Pointer.h +++ b/clang/lib/AST/Interp/Pointer.h @@ -98,7 +98,7 @@ class Pointer { } /// Converts the pointer to an APValue that is an rvalue. - APValue toRValue(const Context &Ctx) const; + std::optional<APValue> toRValue(const Context &Ctx) const; /// Offsets a pointer inside an array. [[nodiscard]] Pointer atIndex(unsigned Idx) const { @@ -379,6 +379,7 @@ class Pointer { return *reinterpret_cast<T *>(Pointee->rawData() + Base + sizeof(InitMapPtr)); + assert(Offset + sizeof(T) <= Pointee->getDescriptor()->getAllocSize()); return *reinterpret_cast<T *>(Pointee->rawData() + Offset); } diff --git a/clang/test/AST/Interp/literals.cpp b/clang/test/AST/Interp/literals.cpp index ba24955d14503be..3ff4d4364c27314 100644 --- a/clang/test/AST/Interp/literals.cpp +++ b/clang/test/AST/Interp/literals.cpp @@ -261,15 +261,13 @@ namespace SizeOf { #if __cplusplus >= 202002L /// FIXME: The following code should be accepted. consteval int foo(int n) { // ref-error {{consteval function never produces a constant expression}} - return sizeof(int[n]); // ref-note 3{{not valid in a constant expression}} \ - // expected-note {{not valid in a constant expression}} + return sizeof(int[n]); // ref-note 3{{not valid in a constant expression}} } constinit int var = foo(5); // ref-error {{not a constant expression}} \ // ref-note 2{{in call to}} \ // ref-error {{does not have a constant initializer}} \ // ref-note {{required by 'constinit' specifier}} \ // expected-error {{is not a constant expression}} \ - // expected-note {{in call to}} \ // expected-error {{does not have a constant initializer}} \ // expected-note {{required by 'constinit' specifier}} \ diff --git a/clang/test/AST/Interp/records.cpp b/clang/test/AST/Interp/records.cpp index 280eaf34898ceca..dd99152c87ddf3b 100644 --- a/clang/test/AST/Interp/records.cpp +++ b/clang/test/AST/Interp/records.cpp @@ -170,7 +170,6 @@ class Bar { // expected-note {{definition of 'Bar' is not complete}} \ // ref-error {{has incomplete type 'const Bar'}} }; constexpr Bar B; // expected-error {{must be initialized by a constant expression}} \ - // expected-error {{failed to evaluate an expression}} \ // ref-error {{must be initialized by a constant expression}} constexpr Bar *pb = nullptr; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits