https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/189410
>From d49367a5e0a1e72dfaed8b25306d557270f40a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Tue, 10 Feb 2026 16:06:17 +0100 Subject: [PATCH] Exceptions --- .../include/clang/Basic/DiagnosticASTKinds.td | 5 + clang/lib/AST/ByteCode/ByteCodeEmitter.cpp | 3 +- clang/lib/AST/ByteCode/ByteCodeEmitter.h | 9 + clang/lib/AST/ByteCode/Compiler.cpp | 221 +++- clang/lib/AST/ByteCode/Compiler.h | 3 + clang/lib/AST/ByteCode/Context.cpp | 3 + clang/lib/AST/ByteCode/Context.h | 2 + clang/lib/AST/ByteCode/Disasm.cpp | 8 + clang/lib/AST/ByteCode/EvalEmitter.h | 10 + clang/lib/AST/ByteCode/Exceptions.cpp | 56 + clang/lib/AST/ByteCode/Exceptions.h | 51 + clang/lib/AST/ByteCode/Function.cpp | 9 + clang/lib/AST/ByteCode/Function.h | 11 +- clang/lib/AST/ByteCode/Interp.cpp | 163 ++- clang/lib/AST/ByteCode/Interp.h | 77 +- clang/lib/AST/ByteCode/InterpFrame.h | 4 - clang/lib/AST/ByteCode/InterpStack.h | 2 + clang/lib/AST/ByteCode/InterpState.cpp | 8 + clang/lib/AST/ByteCode/InterpState.h | 3 + clang/lib/AST/ByteCode/Opcodes.td | 26 + clang/lib/AST/ByteCode/PrimType.h | 10 + clang/lib/AST/CMakeLists.txt | 1 + clang/test/AST/ByteCode/cxx20.cpp | 15 +- clang/test/AST/ByteCode/cxx23.cpp | 4 +- clang/test/AST/ByteCode/exceptions.cpp | 1162 +++++++++++++++++ clang/test/AST/ByteCode/invalid.cpp | 30 +- 26 files changed, 1826 insertions(+), 70 deletions(-) create mode 100644 clang/lib/AST/ByteCode/Exceptions.cpp create mode 100644 clang/lib/AST/ByteCode/Exceptions.h create mode 100644 clang/test/AST/ByteCode/exceptions.cpp diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index f86f0157b2b1f..2f7082ba20ced 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -408,6 +408,11 @@ def note_constexpr_infer_alloc_token_no_metadata : Note< "could not get token metadata for inferred type">; def note_constexpr_infer_alloc_token_stateful_mode : Note<"stateful alloc token mode not supported in constexpr">; +def note_constexpr_uncaught_exception : Note<"uncaught exception of type %0: '%1'">; +def note_constexpr_exception_in_noexcept_func : Note<"uncaught exception in noexcept function">; +def note_constexpr_no_active_exception : Note<"rethrow with no active exception">; +def note_constexpr_throw_with_active_exception : Note<"throw while another exception is already active">; +def note_previous_throw : Note<"currently active exception thrown here">; def warn_attribute_needs_aggregate : Warning< "%0 attribute is ignored in non-aggregate type %1">, diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp index 393b8481fecd1..17cf076c52851 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp @@ -85,7 +85,8 @@ void ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl, // Set the function's code. Func->setCode(FuncDecl, NextLocalOffset, std::move(Code), std::move(SrcMap), - std::move(Scopes), FuncDecl->hasBody(), IsValid); + std::move(Scopes), std::move(ExceptionTable), + FuncDecl->hasBody(), IsValid); Func->setIsFullyCompiled(true); } diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h index 34342e53837b9..0cdd5d2d1dfa5 100644 --- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h +++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h @@ -82,6 +82,14 @@ class ByteCodeEmitter { llvm::SmallVector<SmallVector<Local, 8>, 2> Descriptors; std::optional<SourceInfo> LocOverride = std::nullopt; + unsigned currentCodeSize() const { return Code.size(); } + + void registerExceptionHandler(unsigned From, unsigned To, unsigned Target, + UnsignedOrNone DeclOffset, const Type *T) { + ExceptionTable.push_back( + ExceptionTableEntry{From, To, Target, DeclOffset, T}); + } + private: /// Current compilation context. Context &Ctx; @@ -97,6 +105,7 @@ class ByteCodeEmitter { llvm::DenseMap<LabelTy, llvm::SmallVector<unsigned, 5>> LabelRelocs; /// Program code. llvm::SmallVector<std::byte> Code; + llvm::SmallVector<ExceptionTableEntry> ExceptionTable; /// Opcode to expression mapping. SourceMap SrcMap; diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 46762ed8291f9..273dadbf50634 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -50,6 +50,17 @@ static bool isSideEffectFree(const Expr *E) { return false; } +[[maybe_unused]] static bool blockEndsInReturn(const Stmt *S) { + if (isa<ReturnStmt>(S)) + return true; + + if (const auto *CS = dyn_cast<CompoundStmt>(S); CS && !CS->body_empty()) { + return isa<ReturnStmt>(CS->body_back()); + } + + return false; +} + /// Scope chain managing the variable lifetimes. template <class Emitter> class VariableScope { public: @@ -3746,10 +3757,134 @@ bool Compiler<Emitter>::VisitPredefinedExpr(const PredefinedExpr *E) { template <class Emitter> bool Compiler<Emitter>::VisitCXXThrowExpr(const CXXThrowExpr *E) { - if (E->getSubExpr() && !this->discard(E->getSubExpr())) + const Expr *SubExpr = E->getSubExpr(); + if (!Ctx.ExceptionsEnabled) { + if (SubExpr && !this->discard(SubExpr)) + return false; + return this->emitInvalid(E); + } + + if (!SubExpr) + return this->emitReThrow(E); + + QualType ExceptionType = SubExpr->getType(); + OptPrimType ExceptionT = classify(SubExpr); + + const Descriptor *Desc; + if (ExceptionT) + Desc = P.createDescriptor(SubExpr, *ExceptionT); + else + Desc = P.createDescriptor(SubExpr, ExceptionType.getTypePtr(), std::nullopt, + /*IsConst=*/false); + + if (!this->emitAllocException(Desc, E)) + return false; + + if (ExceptionT) { + if (!this->visit(SubExpr)) + return false; + if (!this->emitInit(*ExceptionT, E)) + return false; + } else { + if (!this->visitInitializer(SubExpr)) + return false; + } + + OptPrimType T = classify(E->getSubExpr()->getType()); + if (!this->emitSaveException(ExceptionType.getTypePtr(), T, E)) return false; - return this->emitInvalid(E); + this->VarScope->destroyLocals(); + + return this->emitThrow(E); +} + +template <class Emitter> +bool Compiler<Emitter>::visitCXXTryStmt(const CXXTryStmt *S) { + if (!Ctx.ExceptionsEnabled) { + // Ignore all handlers. + return this->visitStmt(S->getTryBlock()); + } + + unsigned NumHandlers = S->getNumHandlers(); + + // When an exception is thrown in the middle of a try{} block, we use this + // throw trap to pop all values from the stack that have been added during + // the try block before the throw. + if (!this->emitThrowTrap(S)) + return false; + + // For the try block, we record the bytecode offset before and + // after it. When an exception is thrown, we check if the offset + // at that point is between the start/end of the appropriate catch + // handler for this try block. If we find such a handler, we jump to it. + unsigned TryBlockStart = this->currentCodeSize(); + { + const auto *TryBlock = cast<CompoundStmt>(S->getTryBlock()); + if (!this->visitStmt(TryBlock)) + return false; + } + unsigned TryBlockEnd = this->currentCodeSize(); + + // Jump after handlers if nothing was thrown. + LabelTy EndLabel = this->getLabel(); + this->jump(EndLabel, S); + + // Register and emit all handlers. + for (unsigned I = 0; I != NumHandlers; ++I) { + const CXXCatchStmt *Handler = S->getHandler(I); + const Stmt *HandlerBlock = Handler->getHandlerBlock(); + const VarDecl *ExceptionDecl = Handler->getExceptionDecl(); + QualType CatchType = Handler->getCaughtType(); + UnsignedOrNone ExceptionDeclOffset = std::nullopt; + + unsigned HandlerOffset = this->currentCodeSize(); + if (ExceptionDecl) { + if (OptPrimType T = classify(CatchType)) { + unsigned LocalOffset = allocateLocalPrimitive(ExceptionDecl, *T, + /*IsConst=*/true); + if (CatchType->isReferenceType()) { + if (!this->emitGetPtrExceptionValue(S)) + return false; + } else { + if (!this->emitGetExceptionValue(*T, S)) + return false; + } + if (!this->emitSetLocal(*T, LocalOffset, S)) + return false; + } else { + UnsignedOrNone LocalOffset = allocateLocal(ExceptionDecl, CatchType); + if (!LocalOffset) + return false; + + if (!this->emitGetPtrLocal(*LocalOffset, Handler)) + return false; + if (!this->emitGetPtrExceptionValue(Handler)) + return false; + if (!this->emitMemcpy(Handler)) + return false; + if (!this->emitPopPtr(Handler)) + return false; + } + } else { + // This is a catch-all handler. + } + const Type *CatchTypePtr = CatchType.getTypePtrOrNull(); + + this->registerExceptionHandler(TryBlockStart, TryBlockEnd, HandlerOffset, + ExceptionDeclOffset, CatchTypePtr); + if (!this->visitStmt(HandlerBlock)) + return false; + + if (blockEndsInReturn(HandlerBlock)) + continue; + this->jump(EndLabel, S); + } + + this->fallthrough(EndLabel); + this->emitLabel(EndLabel); + + return true; } template <class Emitter> @@ -7167,12 +7302,6 @@ bool Compiler<Emitter>::visitAttributedStmt(const AttributedStmt *S) { return true; } -template <class Emitter> -bool Compiler<Emitter>::visitCXXTryStmt(const CXXTryStmt *S) { - // Ignore all handlers. - return this->visitStmt(S->getTryBlock()); -} - template <class Emitter> bool Compiler<Emitter>::emitLambdaStaticInvokerBody(const CXXMethodDecl *MD) { assert(MD->isLambdaStaticInvoker()); @@ -7233,11 +7362,9 @@ bool Compiler<Emitter>::emitLambdaStaticInvokerBody(const CXXMethodDecl *MD) { return false; this->emitCleanup(); - if (ReturnType) - return this->emitRet(*ReturnType, MD); - // Nothing to do, since we emitted the RVO pointer above. - return this->emitRetVoid(MD); + bool CanThrow = MD->getType()->getAs<FunctionProtoType>()->canThrow(); + return this->emitFunctionReturn(MD, ReturnType, CanThrow); } template <class Emitter> @@ -7443,11 +7570,21 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { return false; } - if (!visitStmt(Body)) - return false; + if (isa<CompoundStmt>(Body)) { + if (!visitStmt(Body)) + return false; + } else { + // direct try {} body. + LocalScope<Emitter> Scope(this); + if (!visitStmt(Body)) + return false; + if (!Scope.destroyLocals()) + return false; + } } - return this->emitRetVoid(SourceInfo{}); + bool CanThrow = Ctor->getType()->getAs<FunctionProtoType>()->canThrow(); + return this->emitFunctionReturn(Ctor, ReturnType, CanThrow); } template <class Emitter> @@ -7497,7 +7634,11 @@ bool Compiler<Emitter>::compileDestructor(const CXXDestructorDecl *Dtor) { return false; // FIXME: Virtual bases. - return this->emitPopPtr(Dtor) && this->emitRetVoid(Dtor); + if (!this->emitPopPtr(Dtor)) + return false; + + bool CanThrow = Dtor->getType()->getAs<FunctionProtoType>()->canThrow(); + return this->emitFunctionReturn(Dtor, ReturnType, CanThrow); } template <class Emitter> @@ -7540,14 +7681,32 @@ bool Compiler<Emitter>::visitFunc(const FunctionDecl *F) { } // Regular functions. - if (const auto *Body = F->getBody()) - if (!visitStmt(Body)) - return false; + if (const auto *Body = F->getBody()) { + if (isa<CompoundStmt>(Body)) { + if (!visitStmt(Body)) + return false; + } else { + // direct try {} body. + LocalScope<Emitter> Scope(this); + if (!visitStmt(Body)) + return false; + if (!Scope.destroyLocals()) + return false; + } + } // Emit a guard return to protect against a code path missing one. - if (F->getReturnType()->isVoidType()) - return this->emitRetVoid(SourceInfo{}); - return this->emitNoRet(SourceInfo{}); + if (F->getReturnType()->isVoidType()) { + if (!this->emitRetVoid(SourceInfo{})) + return false; + } else if (!this->emitNoRet(SourceInfo{})) { + return false; + } + + bool CanThrow = F->getType()->getAs<FunctionProtoType>()->canThrow(); + if (Ctx.ExceptionsEnabled && CanThrow) + return this->emitAfterRet(SourceInfo{}); + return true; } static uint32_t getBitWidth(const Expr *E) { @@ -8674,6 +8833,24 @@ bool Compiler<Emitter>::emitBuiltinBitCast(const CastExpr *E) { return true; } +template <class Emitter> +bool Compiler<Emitter>::emitFunctionReturn(const FunctionDecl *FD, + OptPrimType ReturnType, + bool CanThrow) { + bool NeedsAfterRet = Ctx.ExceptionsEnabled && CanThrow; + + if (!NeedsAfterRet) { + if (ReturnType) + return this->emitRet(*ReturnType, FD); + + return this->emitRetVoid(FD); + } + + if (ReturnType) + return this->emitRet(*ReturnType, FD) && this->emitAfterRet(FD); + return this->emitRetVoid(FD) && this->emitAfterRet(FD); +} + /// Replicate a scalar value into every scalar element of an aggregate. /// The scalar is stored in a local at \p SrcOffset and a pointer to the /// destination must be on top of the interpreter stack. Each element receives diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index a93fdd20b712f..6d740a1bcaaa7 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -462,6 +462,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool refersToUnion(const Expr *E); + bool emitFunctionReturn(const FunctionDecl *FD, OptPrimType ReturnType, + bool CanThrow = false); + protected: /// Variable to storage mapping. llvm::DenseMap<const ValueDecl *, Scope::Local> Locals; diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp index 4ca76ce3669d3..68c3c819179e0 100644 --- a/clang/lib/AST/ByteCode/Context.cpp +++ b/clang/lib/AST/ByteCode/Context.cpp @@ -33,6 +33,9 @@ Context::Context(ASTContext &Ctx) : Ctx(Ctx), P(new Program(*this)) { this->LongLongWidth = Ctx.getTargetInfo().getLongLongWidth(); assert(Ctx.getTargetInfo().getCharWidth() == 8 && "We're assuming 8 bit chars"); + + this->ExceptionsEnabled = + Ctx.getLangOpts().CPlusPlus26 && Ctx.getLangOpts().CXXExceptions; } Context::~Context() = default; diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h index 47821a3e3a7f3..9b0a6976894cf 100644 --- a/clang/lib/AST/ByteCode/Context.h +++ b/clang/lib/AST/ByteCode/Context.h @@ -46,6 +46,8 @@ class EvalIDScope; /// Holds all information required to evaluate constexpr code in a module. class Context final { public: + unsigned ExceptionsEnabled : 1; + /// Initialises the constexpr VM. explicit Context(ASTContext &Ctx); diff --git a/clang/lib/AST/ByteCode/Disasm.cpp b/clang/lib/AST/ByteCode/Disasm.cpp index 4caf830a0a1b4..419b5a4ef5ada 100644 --- a/clang/lib/AST/ByteCode/Disasm.cpp +++ b/clang/lib/AST/ByteCode/Disasm.cpp @@ -165,6 +165,14 @@ LLVM_DUMP_METHOD void Function::dump(llvm::raw_ostream &OS, OS << "rvo: " << hasRVO() << "\n"; OS << "this arg: " << hasThisPointer() << "\n"; + // Print exception table. + if (!ExceptionTable.empty()) { + for (auto &EE : ExceptionTable) { + OS << EE.CodeStart << ".." << EE.CodeEnd << " -> " << EE.Target << " (" + << EE.CatchType << ")\n"; + } + } + struct OpText { size_t Addr; std::string Op; diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h index f939ef4839a19..4e2bc2da5556f 100644 --- a/clang/lib/AST/ByteCode/EvalEmitter.h +++ b/clang/lib/AST/ByteCode/EvalEmitter.h @@ -62,6 +62,16 @@ class EvalEmitter : public SourceMapper { virtual ~EvalEmitter(); + unsigned currentCodeSize() const { + llvm_unreachable("Should never be called on EvalEmitter"); + return 0; + } + + void registerExceptionHandler(unsigned From, unsigned To, unsigned Target, + UnsignedOrNone, const Type *T) { + llvm_unreachable("Should never be called on EvalEmitter"); + } + /// Define a label. void emitLabel(LabelTy Label); /// Create a label. diff --git a/clang/lib/AST/ByteCode/Exceptions.cpp b/clang/lib/AST/ByteCode/Exceptions.cpp new file mode 100644 index 0000000000000..ad8f5476f16a0 --- /dev/null +++ b/clang/lib/AST/ByteCode/Exceptions.cpp @@ -0,0 +1,56 @@ +//===-------------------------- Exceptions.cpp ------------------*- 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 "Exceptions.h" +#include "clang/AST/ASTContext.h" + +using namespace clang; +using namespace clang::interp; + +bool ExceptionTableEntry::canCatch(const Type *ThrowType) const { + const Type *CatchType = this->CatchType; + + if (!CatchType || ASTContext::hasSameType(CatchType, ThrowType)) + return true; + + assert(CatchType); + + // nullptr_t can be caught by any pointer type. + if (ThrowType->isNullPtrType() && CatchType->isPointerType()) + return true; + + // void* can catch all thown pointer types. + if (ThrowType->isPointerType() && CatchType->isVoidPointerType()) + return true; + + if (CatchType->isPointerType() && !ThrowType->isPointerType()) + return false; + + if (CatchType->isPointerOrReferenceType()) + CatchType = CatchType->getPointeeType().getTypePtr(); + if (ThrowType->isPointerOrReferenceType()) + ThrowType = ThrowType->getPointeeType().getTypePtr(); + + if (CatchType == ThrowType) + return true; + + if (CatchType->isRecordType() && ThrowType->isRecordType()) { + const CXXRecordDecl *CatchDecl = CatchType->getAsCXXRecordDecl(); + const CXXRecordDecl *ThrowDecl = ThrowType->getAsCXXRecordDecl(); + assert(CatchDecl); + assert(ThrowDecl); + + if (CatchDecl == ThrowDecl) + return true; + + if (ThrowDecl->isDerivedFrom(CatchDecl)) + return true; + } + + return false; +} diff --git a/clang/lib/AST/ByteCode/Exceptions.h b/clang/lib/AST/ByteCode/Exceptions.h new file mode 100644 index 0000000000000..c8a15ce879ef9 --- /dev/null +++ b/clang/lib/AST/ByteCode/Exceptions.h @@ -0,0 +1,51 @@ +//===----------------------- Exceptions.h ----------------------*- 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_EXCEPTIONS_H +#define LLVM_CLANG_AST_INTERP_EXCEPTIONS_H + +#include "PrimType.h" +#include "clang/Basic/OptionalUnsigned.h" +#include "clang/Basic/SourceLocation.h" + +namespace clang { +class Type; + +namespace interp { +class Block; + +struct ExceptionTableEntry { + unsigned CodeStart; + unsigned CodeEnd; + unsigned Target; + UnsignedOrNone DeclOffset; + /// If CatchType is nullptr, this is a catch-all handler. + const Type *CatchType; + + /// Check if this exception table entry can catch an exception thrown of the + /// given type. + bool canCatch(const Type *ThrowType) const; +}; + +/// A thrown value. +struct ThrowValue { + const Type *Ty; + Block *B; + SourceLocation Loc; + OptPrimType T; + unsigned CastOffset = 0; + bool Caught = false; + + explicit ThrowValue(const Type *Ty, SourceLocation Loc, Block *B, + OptPrimType T) + : Ty(Ty), B(B), Loc(Loc), T(T) {} +}; + +} // namespace interp +} // namespace clang +#endif diff --git a/clang/lib/AST/ByteCode/Function.cpp b/clang/lib/AST/ByteCode/Function.cpp index cd0f7eb125230..02d5c9b13fa78 100644 --- a/clang/lib/AST/ByteCode/Function.cpp +++ b/clang/lib/AST/ByteCode/Function.cpp @@ -69,3 +69,12 @@ SourceInfo Function::getSource(CodePtr PC) const { return SrcMap.back().second; return It->second; } + +std::optional<ExceptionTableEntry> +Function::findCatchHandler(unsigned CodeOffset, const Type *Ty) const { + for (const auto &E : ExceptionTable) { + if (CodeOffset >= E.CodeStart && CodeOffset <= E.CodeEnd && E.canCatch(Ty)) + return E; + } + return std::nullopt; +} diff --git a/clang/lib/AST/ByteCode/Function.h b/clang/lib/AST/ByteCode/Function.h index 27edfc6d08916..0f885a7884cb4 100644 --- a/clang/lib/AST/ByteCode/Function.h +++ b/clang/lib/AST/ByteCode/Function.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_AST_INTERP_FUNCTION_H #include "Descriptor.h" +#include "Exceptions.h" #include "Source.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" @@ -252,6 +253,9 @@ class Function final { return ArgSize - (align(primSize(PT_Ptr)) * (hasThisPointer() + hasRVO())); } + std::optional<ExceptionTableEntry> findCatchHandler(unsigned CodeOffset, + const Type *Ty) const; + private: /// Construct a function representing an actual function. Function(Program &P, FunctionDeclTy Source, unsigned ArgSize, @@ -261,13 +265,15 @@ class Function final { /// Sets the code of a function. void setCode(FunctionDeclTy Source, unsigned NewFrameSize, llvm::SmallVector<std::byte> &&NewCode, SourceMap &&NewSrcMap, - llvm::SmallVector<Scope, 2> &&NewScopes, bool NewHasBody, - bool NewIsValid) { + llvm::SmallVector<Scope, 2> &&NewScopes, + llvm::SmallVector<ExceptionTableEntry> &&ExceptionTable, + bool NewHasBody, bool NewIsValid) { this->Source = Source; FrameSize = NewFrameSize; Code = std::move(NewCode); SrcMap = std::move(NewSrcMap); Scopes = std::move(NewScopes); + this->ExceptionTable = std::move(ExceptionTable); IsValid = NewIsValid; HasBody = NewHasBody; } @@ -298,6 +304,7 @@ class Function final { llvm::SmallVector<Scope, 2> Scopes; /// List of all parameters, including RVO and instance pointer. llvm::SmallVector<ParamDescriptor> ParamDescriptors; + llvm::SmallVector<ExceptionTableEntry> ExceptionTable; /// Flag to indicate if the function is valid. LLVM_PREFERRED_TYPE(bool) unsigned IsValid : 1; diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index b2af27fc88542..dd3840d5e62f1 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -234,6 +234,25 @@ static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { namespace clang { namespace interp { + +bool diagnoseUncaughtException(InterpState &S, CodePtr OpPC) { + assert(S.ThrownValue); + QualType UncaughtType = QualType(S.ThrownValue->Ty, 0); + std::string ValString; + + if (S.ThrownValue->T) { + TYPE_SWITCH(*S.ThrownValue->T, { + ValString = + S.ThrownValue->B->deref<T>().toDiagnosticString(S.getASTContext()); + }); + } else { + ValString = Pointer(S.ThrownValue->B).toDiagnosticString(S.getASTContext()); + } + S.FFDiag(S.ThrownValue->Loc, diag::note_constexpr_uncaught_exception) + << UncaughtType << ValString; + return false; +} + PRESERVE_NONE static bool BCP(InterpState &S, CodePtr OpPC, int32_t Offset, PrimType PT); @@ -1818,6 +1837,141 @@ static void compileFunction(InterpState &S, const Function *Func) { .compileFunc(Definition, const_cast<Function *>(Func)); } +// We have a saved thrown value in InterpState. +// Now try to catch that value in the current function. +// If no corresponding catch handler is found, jump to the +// AfterRet op at the end of the function. +static bool catchException(InterpState &S, CodePtr OpPC) { + assert(S.getContext().ExceptionsEnabled); + assert(S.ThrownValue); + assert(!S.ThrownValue->Caught); + + // We've reached the bottom frame. We can't go any higher, so diagnose + // an uncaught exception. + const Function *CurrFunction = S.Current->getFunction(); + if (!CurrFunction) { + assert(S.Current->isBottomFrame()); + if (!S.checkingPotentialConstantExpression()) + return diagnoseUncaughtException(S, OpPC); + return false; + } + + unsigned CodeOffset = S.PC - CurrFunction->getCodeBegin(); + std::optional<ExceptionTableEntry> CatchEntry = + CurrFunction->findCatchHandler(CodeOffset, S.ThrownValue->Ty); + + if (!CatchEntry) { + // We didn't find an appropriate catch handler in the current function. + // Skip to the end of the function. + bool CanThrow = S.Current->getFunction() + ->getDecl() + ->getType() + ->getAs<FunctionProtoType>() + ->canThrow(); + if (!CanThrow) { + S.CCEDiag(S.Current->getSource(OpPC), + diag::note_constexpr_exception_in_noexcept_func); + return false; + } + // Jump to the end of the function. The calling function will handle + // catching the exception. + S.PC = S.Current->getFunction()->getCodeEnd() - align(sizeof(Opcode)); +#ifndef NDEBUG + CodePtr PCCopy = S.PC; + Opcode Op = PCCopy.read<Opcode>(); + assert(Op == OP_AfterRet); +#endif + return true; + } + + // We *did* find a catch handler. We now need to cast the thrown value to + // the correct type, if necessary. + + // NB: CaughtType may be null (for catch-all handlers). + const Type *CaughtType = CatchEntry->CatchType; + const Type *ThrownType = S.ThrownValue->Ty; + assert(ThrownType); + + // There might be some values left on the stack that have been added in + // between entering the try{} block and the throw statement. We need to + // remove all of those so the stack is in a proper state after the catch + // handler finishes. + while (S.Stk.size() != S.ThrowTrapStackSize) { + S.Stk.discardSlow(); + } + + if (CaughtType && CaughtType->isPointerOrReferenceType()) + CaughtType = CaughtType->getPointeeType().getTypePtr(); + if (ThrownType->isPointerOrReferenceType()) + ThrownType = ThrownType->getPointeeType().getTypePtr(); + + bool NeedsCast = CaughtType && + !ASTContext::hasSameType(CaughtType, ThrownType) && + CaughtType->isRecordType() && ThrownType->isRecordType(); + + if (!NeedsCast) { + // We dont need to change the value at all. Just jump to the exception table + // entry. + S.PC = CurrFunction->getCodeBegin() + CatchEntry->Target; + S.ThrownValue->Caught = true; + return true; + } + + unsigned BaseOffset = S.getContext().collectBaseOffset( + CaughtType->getAsRecordDecl(), ThrownType->getAsRecordDecl()); + + S.ThrownValue->CastOffset = BaseOffset; + S.ThrownValue->Caught = true; + // Jump to the catch handler in this function. + S.PC = CurrFunction->getCodeBegin() + CatchEntry->Target; + + return true; +} + +bool Throw(InterpState &S, CodePtr OpPC) { + assert(S.getContext().ExceptionsEnabled); + return catchException(S, OpPC); +} + +bool ReThrow(InterpState &S, CodePtr OpPC) { + assert(S.getContext().ExceptionsEnabled); + + if (!S.ThrownValue) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_no_active_exception); + return false; + } + + assert(S.ThrownValue); + S.ThrownValue->Caught = false; + return catchException(S, OpPC); +} + +bool SaveException(InterpState &S, CodePtr OpPC, const Type *Ty, + OptPrimType T) { + assert(S.getContext().ExceptionsEnabled); + const Pointer &Ptr = S.Stk.pop<Pointer>(); + + // Must've been allocated via AllocException + assert(Ptr.block()->getEvalID() == ~0u); + assert(Ptr.block()->isInitialized()); + + // If we already have a thrown exception, it must be caught already, + // in which case we simply override it below. + // If it isn't caught, we need to diagnose. + if (S.ThrownValue && !S.ThrownValue->Caught) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_throw_with_active_exception, 1); + S.Note(S.ThrownValue->Loc, diag::note_previous_throw); + return false; + } + + SourceLocation Loc = S.Current->getLocation(OpPC); + S.ThrownValue = std::make_unique<ThrowValue>( + Ty, Loc, const_cast<Block *>(Ptr.block()), T); + return true; +} + bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize) { if (Func->hasThisPointer()) { @@ -1857,6 +2011,9 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, InterpStateCCOverride CCOverride(S, Func->isImmediate()); if (Interpret(S)) { + if (S.ThrownValue && !S.ThrownValue->Caught) + return catchException(S, OpPC); + assert(S.Current == FrameBefore); return true; } @@ -1867,6 +2024,7 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func, S.Current = FrameBefore; return false; } + bool Call(InterpState &S, CodePtr OpPC, const Function *Func, uint32_t VarArgSize) { @@ -1962,6 +2120,9 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func, return false; } + if (S.ThrownValue && !S.ThrownValue->Caught) + return catchException(S, OpPC); + assert(S.Current == FrameBefore); return true; } @@ -3182,7 +3343,7 @@ constexpr bool OpReturns(Opcode Op) { Op == OP_RetSint64 || Op == OP_RetUint64 || Op == OP_RetIntAP || Op == OP_RetIntAPS || Op == OP_RetBool || Op == OP_RetFixedPoint || Op == OP_RetPtr || Op == OP_RetMemberPtr || Op == OP_RetFloat || - Op == OP_EndSpeculation; + Op == OP_EndSpeculation || Op == OP_AfterRet; } #if USE_TAILCALLS diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 201679017c98b..c94a46b2480de 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -86,6 +86,8 @@ bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, bool DiagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, const Block *B, AccessKinds AK); +bool diagnoseUncaughtException(InterpState &S, CodePtr OpPC); + /// Checks a direct load of a primitive value from a global or local variable. bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B); bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B); @@ -152,6 +154,9 @@ bool CastFloatingIntegralAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth, bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth, uint32_t FPOI); +bool Throw(InterpState &S, CodePtr OpPC); +bool ReThrow(InterpState &S, CodePtr OpPC); + enum class ShiftDir { Left, Right }; enum class ShiftFailure { @@ -315,6 +320,72 @@ PRESERVE_NONE inline bool RetVoid(InterpState &S) { return true; } +// Inserted at the very end of functions that can throw, if exceptions are +// enabled. We rewind the stack to the start of the function frame and return +// nothing. If we arrive at an AfterRet op, we must have thrown an exception. +PRESERVE_NONE inline bool AfterRet(InterpState &S) { + assert(S.getContext().ExceptionsEnabled); + assert(S.ThrownValue); + + while (S.Stk.size() != S.Current->getFrameOffset()) + S.Stk.discardSlow(); + + return RetVoid(S); +} + +bool SaveException(InterpState &S, CodePtr OpPC, const Type *Ty, OptPrimType T); + +inline bool ThrowTrap(InterpState &S) { + assert(S.getContext().ExceptionsEnabled); + S.ThrowTrapStackSize = S.Stk.size(); + return true; +} + +/// Allocate memory to store an exception object. +inline bool AllocException(InterpState &S, const Descriptor *Desc) { + assert(S.getContext().ExceptionsEnabled); + assert(Desc); + char *Memory = (char *)S.allocate(sizeof(Block) + Desc->getAllocSize()); + Block *B = new (Memory) Block(~0u, Desc); + B->invokeCtor(); + S.Stk.push<Pointer>(B); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool GetExceptionValue(InterpState &S) { + assert(S.getContext().ExceptionsEnabled); + assert(S.ThrownValue); + assert(S.ThrownValue->T && "Thrown value must be primitive"); + + // The catch handler might require a cast of the thrown value. + // The actual cast happens in catchException(), here we apply + // the offset to the pointer (also in GetPtrExceptionValue). + if constexpr (std::is_same_v<T, Pointer>) { + if (S.ThrownValue->CastOffset != 0) + S.Stk.push<Pointer>(S.ThrownValue->B->deref<Pointer>().atField( + S.ThrownValue->CastOffset)); + else + S.Stk.push<Pointer>(S.ThrownValue->B->deref<Pointer>()); + return true; + } + + S.Stk.push<T>(S.ThrownValue->B->deref<T>()); + return true; +} + +inline bool GetPtrExceptionValue(InterpState &S) { + assert(S.getContext().ExceptionsEnabled); + assert(S.ThrownValue); + + Pointer P = Pointer(S.ThrownValue->B); + if (S.ThrownValue->CastOffset != 0) + S.Stk.push<Pointer>(P.atField(S.ThrownValue->CastOffset)); + else + S.Stk.push<Pointer>(P); + return true; +} + //===----------------------------------------------------------------------===// // Add, Sub, Mul //===----------------------------------------------------------------------===// @@ -2393,7 +2464,8 @@ bool Init(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckInit(S, OpPC, Ptr)) return false; - Ptr.initialize(); + if (Ptr.canBeInitialized()) + Ptr.initialize(); new (&Ptr.deref<T>()) T(Value); return true; } @@ -2404,7 +2476,8 @@ bool InitPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckInit(S, OpPC, Ptr)) return false; - Ptr.initialize(); + if (Ptr.canBeInitialized()) + Ptr.initialize(); new (&Ptr.deref<T>()) T(Value); return true; } diff --git a/clang/lib/AST/ByteCode/InterpFrame.h b/clang/lib/AST/ByteCode/InterpFrame.h index 9498a911f2892..109b1a7e3189f 100644 --- a/clang/lib/AST/ByteCode/InterpFrame.h +++ b/clang/lib/AST/ByteCode/InterpFrame.h @@ -92,10 +92,8 @@ class InterpFrame final : public Frame { /// Returns the current function. const Function *getFunction() const { return Func; } -#ifndef NDEBUG /// Returns the offset on the stack at which the frame starts. size_t getFrameOffset() const { return FrameOffset; } -#endif /// Returns the value of a local variable. template <typename T> const T &getLocal(unsigned Offset) const { @@ -231,10 +229,8 @@ class InterpFrame final : public Frame { const unsigned ArgSize; /// Pointer to the arguments in the callee's frame. char *Args = nullptr; -#ifndef NDEBUG /// Offset on the stack at entry. size_t FrameOffset = 0; -#endif public: unsigned MSVCConstexprAllowed = 0; diff --git a/clang/lib/AST/ByteCode/InterpStack.h b/clang/lib/AST/ByteCode/InterpStack.h index 2c02979ee6eec..b877a11abaae6 100644 --- a/clang/lib/AST/ByteCode/InterpStack.h +++ b/clang/lib/AST/ByteCode/InterpStack.h @@ -57,6 +57,8 @@ class InterpStack final { } shrink(aligned_size<T>()); } + + /// Like discard(), but not type-aware. Avoid using this. void discardSlow(); /// Returns a reference to the value on the top of the stack. diff --git a/clang/lib/AST/ByteCode/InterpState.cpp b/clang/lib/AST/ByteCode/InterpState.cpp index f6338cda56e34..5d5fbb5f3d4ec 100644 --- a/clang/lib/AST/ByteCode/InterpState.cpp +++ b/clang/lib/AST/ByteCode/InterpState.cpp @@ -51,6 +51,14 @@ bool InterpState::inConstantContext() const { } InterpState::~InterpState() { + // Invoke the dtor func of the allocated exception object block. + if (ThrownValue) { + Block *B = ThrownValue->B; + if (B && B->isInitialized()) + B->invokeDtor(); + ThrownValue = nullptr; + } + while (Current && !Current->isBottomFrame()) { InterpFrame *Next = Current->Caller; delete Current; diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h index 050fa4c77cd2f..1a1c314fc89e9 100644 --- a/clang/lib/AST/ByteCode/InterpState.h +++ b/clang/lib/AST/ByteCode/InterpState.h @@ -15,6 +15,7 @@ #include "Context.h" #include "DynamicAllocator.h" +#include "Exceptions.h" #include "Floating.h" #include "Function.h" #include "InterpFrame.h" @@ -199,8 +200,10 @@ class InterpState final : public State, public SourceMapper { const bool InfiniteSteps = false; /// ID identifying this evaluation. const unsigned EvalID; + unsigned ThrowTrapStackSize = 0; EvaluationKind EvalKind = EvaluationKind::None; + std::unique_ptr<ThrowValue> ThrownValue; /// Things needed to do speculative execution. SmallVectorImpl<PartialDiagnosticAt> *PrevDiags = nullptr; diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 1705ad9d9d24b..c1f5c39a72b5f 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -71,6 +71,7 @@ def ArgDesc : ArgType { let Name = "const Descriptor *"; } def ArgPrimType : ArgType { let Name = "PrimType"; } def ArgEnumDecl : ArgType { let Name = "const EnumDecl *"; } def ArgTypePtr : ArgType { let Name = "const Type *"; } +def ArgOptPrimType : ArgType { let Name = "OptPrimType"; } //===----------------------------------------------------------------------===// // Classes of types instructions operate on. @@ -225,6 +226,31 @@ def NoRet : Opcode { } +/// Exceptions +def AfterRet : SuccessOpcode { + let CanReturn = 1; +} + +def Throw : Opcode; +def ReThrow : Opcode; +def SaveException : Opcode { + let Args = [ArgTypePtr, ArgOptPrimType]; +} + +def GetExceptionValue : SuccessOpcode { + let Types = [AllTypeClass]; + let HasGroup = 1; +} +def GetPtrExceptionValue : SuccessOpcode; + +def AllocException : SuccessOpcode { + let Args = [ArgDesc]; +} + +def ThrowTrap : SuccessOpcode; + + +/// Calls def Call : Opcode { let Args = [ArgFunction, ArgUint32]; } diff --git a/clang/lib/AST/ByteCode/PrimType.h b/clang/lib/AST/ByteCode/PrimType.h index 8f725942fedb9..279ec22f0d409 100644 --- a/clang/lib/AST/ByteCode/PrimType.h +++ b/clang/lib/AST/ByteCode/PrimType.h @@ -102,6 +102,16 @@ class OptPrimType final { }; static_assert(sizeof(OptPrimType) == sizeof(PrimType)); +inline OptPrimType getSwappedBytes(OptPrimType T) { return T; } + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OptPrimType T) { + if (!T) + OS << "None"; + else + OS << static_cast<int>(*T); + return OS; +} + enum class CastKind : uint8_t { Reinterpret, ReinterpretLike, diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index e3f74d73f21da..f723af7fa5465 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -75,6 +75,7 @@ add_clang_library(clangAST ByteCode/Descriptor.cpp ByteCode/Disasm.cpp ByteCode/EvalEmitter.cpp + ByteCode/Exceptions.cpp ByteCode/Function.cpp ByteCode/InterpBuiltin.cpp ByteCode/InterpBuiltinBitCast.cpp diff --git a/clang/test/AST/ByteCode/cxx20.cpp b/clang/test/AST/ByteCode/cxx20.cpp index 70ff1045a74da..20801a26389ef 100644 --- a/clang/test/AST/ByteCode/cxx20.cpp +++ b/clang/test/AST/ByteCode/cxx20.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,expected -fcxx-exceptions %s -DNEW_INTERP -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref -fcxx-exceptions %s +// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,expected %s -DNEW_INTERP -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -fcxx-exceptions -std=c++20 -verify=both,ref %s int x; @@ -673,7 +673,7 @@ namespace ConstexprArrayInitLoopExprDestructors struct Highlander { int *p = 0; constexpr Highlander() {} - constexpr void set(int *p) { this->p = p; ++*p; if (*p != 1) throw "there can be only one"; } + constexpr void set(int *p) { this->p = p; ++*p; if (*p != 1) __builtin_abort(); } constexpr ~Highlander() { --*p; } }; @@ -776,7 +776,7 @@ namespace FailingDestructor { constexpr ~D() { if (!can_destroy) - throw "oh no"; + __builtin_abort(); } }; template<D d> @@ -1062,7 +1062,7 @@ namespace OnePastEndDtor { namespace Virtual { struct NonZeroOffset { int padding = 123; }; - constexpr void assert(bool b) { if (!b) throw 0; } + constexpr void assert(bool b) { if (!b) __builtin_abort(); } // Ensure that we pick the right final overrider during construction. struct A { @@ -1190,9 +1190,8 @@ namespace DiscardedTrivialCXXConstructExpr { int x; }; - constexpr int foo(int x) { // ref-error {{never produces a constant expression}} - throw S(3); // both-note {{not valid in a constant expression}} \ - // ref-note {{not valid in a constant expression}} + constexpr int foo(int x) { // both-error {{never produces a constant expression}} + __builtin_abort(); // both-note 2{{not valid in a constant expression}} return 1; } diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp index 5607ec9b59cb5..5b549f1e2d887 100644 --- a/clang/test/AST/ByteCode/cxx23.cpp +++ b/clang/test/AST/ByteCode/cxx23.cpp @@ -129,8 +129,8 @@ namespace StaticOperators { struct S1 { constexpr S1() { // all20-error {{never produces a constant expression}} - throw; // all-note {{not valid in a constant expression}} \ - // all20-note {{not valid in a constant expression}} + __builtin_abort(); // all-note {{not valid in a constant expression}} \ + // all20-note {{not valid in a constant expression}} } static constexpr int operator()() { return 3; } // ref20-warning {{C++23 extension}} \ // expected20-warning {{C++23 extension}} diff --git a/clang/test/AST/ByteCode/exceptions.cpp b/clang/test/AST/ByteCode/exceptions.cpp new file mode 100644 index 0000000000000..3bf43b08a94c1 --- /dev/null +++ b/clang/test/AST/ByteCode/exceptions.cpp @@ -0,0 +1,1162 @@ +// RUN: %clang_cc1 -fcxx-exceptions -std=c++26 -fexperimental-new-constant-interpreter -verify %s + +namespace std { + class exception { + public: + constexpr exception() noexcept {}; + // constexpr exception(const exception&) noexcept; + // constexpr exception& operator=(const exception&) noexcept; + constexpr virtual ~exception() {}; + // constexpr virtual const char* what() const noexcept; + }; + + template <typename T> struct remove_reference { using type = T; }; + template <typename T> struct remove_reference<T &> { using type = T; }; + template <typename T> struct remove_reference<T &&> { using type = T; }; + template <typename T> + constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept { + return static_cast<typename std::remove_reference<T>::type &&>(t); + } +}; + + +class Bad : std::exception {}; + +namespace Simple { + constexpr int a() { + try { + } catch(int e){ + return 12; + } + return -2; + } + static_assert(a() == -2); + + constexpr int b() { + try { + throw 12; + } catch(int e){ + return 12; + } + return -2; + } + static_assert(b() == 12); + + constexpr int c() { + int m = 12; + try { + throw 12; + } catch(int e){ + m = 140; + } + return m; + } + static_assert(c() == 140); + + constexpr int d() { + int m = 12; + try { + throw 12; + } catch(int e){ + m = 140; + } catch (float f) { + m = 15; + } + return m; + } + static_assert(d() == 140); + + constexpr int e() { + int m = 12; + try { + throw 12; + } catch(int e){ + m = e + 2; + } + return m; + } + static_assert(e() == 14); + + constexpr int f() { + int m = 12; + try { + throw 12; + } catch(...){ + m = 100; + } + return m; + } + static_assert(f() == 100); + + constexpr int g() { + int m = 12; + try { + throw Bad(); + } catch(Bad &B){ + m = 100; + } + return m; + } + static_assert(g() == 100); + + constexpr int h() { + int m = 12; + try { + throw ++m; + } catch(...){ + } + return m; + } + static_assert(h() == 13); + + constexpr int i(bool b) { + try { + if (b) + throw 12; + else + throw 14.0f; + } catch (int) { + return 100; + } catch (float) { + return 200; + } + return 0; + } + static_assert(i(true) == 100); + static_assert(i(false) == 200); +} + +namespace Uncaught { + + constexpr int a() { + throw 12; // expected-note {{uncaught exception of type 'int': '12'}} + return 0; + } + static_assert(a() == 13); // expected-error {{not an integral constant expression}} +} + +namespace NoFrame { + static_assert((1, throw 2, 3) == 1); // expected-error {{not an integral constant expression}} \ + // expected-note {{uncaught exception of type 'int': '2'}} \ + // expected-warning {{left operand of comma operator has no effect}} + +} + +namespace CleanupAfterThrowingCall { + constexpr int a() { + throw 12; + return -12; + } + constexpr int test() { + try { + a(); + } catch (int i) { + return 26; + } + + return 120; + } + static_assert(test() == 26); + + constexpr int b2() { + throw 1.0; + return 1; + } + constexpr int a2() { + b2(); + throw 12; + return -12; + } + constexpr int test2() { + + try { + a2(); + } catch (int i) { + return 26; + } catch (double d ){ + return (int)(d * 2); + } + + return 120; + } + static_assert(test2() == 2); +} + +namespace Dtors { + class Inc { + public: + int &m; + constexpr Inc(int &m) : m(m) {} + constexpr ~Inc() { ++m; } + }; + + constexpr int test1() { + int m = 10; + Inc _(m); + + try { + throw 12; + } catch (int) { + return m; + } + } + static_assert(test1() == 10); + + constexpr int test2() { + int m = 10; + try { + Inc _(m); + throw 12; + } catch (int) { + } + return m; + } + static_assert(test2() == 11); + + struct checker { + int & counter; + constexpr ~checker() { + ++counter; + } + }; + + constexpr int test() { + int counter = 0; + { + try { + auto c1 = checker{counter}; + throw 42; + } catch (...) { + return counter * 7; + } + } + return counter * 3; + } + + constexpr int destruction_counter = test(); + static_assert(destruction_counter == 7); +} + +namespace CatchArray { + template <typename T> consteval T test(T head, auto... tail) { + const T array[] = {head, tail...}; + try { + throw array; + } catch (const T (&arr)[5]) { + return -2; + } catch (const T * ptr) { + return *ptr; + } catch (...) { + return -1; + } + } + + constexpr auto r0 = test(1,2,3,4,5,6); + static_assert(r0 == 1); + + constexpr auto r1 = test(1,2,3,4,5); + static_assert(r1 == 1); + + constexpr auto r2 = test(1,2,3,4); + static_assert(r2 == 1); + + constexpr auto r3 = test(7,1,2,3,4,5,6); + static_assert(r3 == 7); + + constexpr auto r4 = test(8,1,2,3,4,5); + static_assert(r4 == 8); + + constexpr auto r5 = test(9,1,2,3,4); + static_assert(r5 == 9); +} + +namespace CatchVoidPtr { + consteval int test() { + int p = 3; + try { + throw &p; + } catch (void * ptr) { + return *static_cast<int *>(ptr); + } + } + static_assert(test() == 3); + + constexpr void t() { + int p = 3; // expected-note {{declared here}} + throw &p; + } + consteval int test2() { + try { + t(); + } catch (void * ptr) { + return *static_cast<int *>(ptr); // expected-note {{read of object outside its lifetime is not allowed in a constant expression}} + } + } + static_assert(test2() == 3); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} +} + +namespace Nullptr { + consteval int test_nullptr() { + try { + throw nullptr; + } catch (const int * ex) { + return true; + } catch (...) { + return false; + } + } + static_assert(test_nullptr()); + + consteval int test_zero() { + try { + throw 0; + } catch (const int * ex) { + return false; + } catch (...) { + return true; + } + } + static_assert(test_zero()); +} + +namespace CatchAll { + template <typename T, typename... Args> consteval int test(Args && ... args) { + try { + throw T{args...}; + } catch (unsigned v) { + return static_cast<int>(v) * 2; + } catch (int v) { + return v * 3; + } catch (bool v) { + return static_cast<int>(v) * 5; + } catch (...) { + return -1; + } + return 0; + } + + static_assert(test<unsigned>(42u) == 84); + static_assert(test<int>(13) == 39); + static_assert(test<bool>(true) == 5); + static_assert(test<long>(42) == -1); +} + +namespace Copy { + class Child {}; + constexpr int test() { + + try { + throw Child{}; + } catch (Child C) { + return 20; + } + return 30; + } + static_assert(test() == 20); + + constexpr int a(){ + throw Child{}; + } + constexpr int test2() { + + try { + a(); + } catch (Child C) { + return 20; + } + return 30; + } + static_assert(test2() == 20); +} + +namespace Inheritance { + class Parent2 { + public: + int F = 5; + constexpr int getFive() { return F; } + }; + class Parent : public Parent2{ + }; + class Child : public Parent {}; + + constexpr int foo() { + try { + throw Child{}; + } catch (Parent2 P) { + return P.getFive() + 9; + } + return 0; + } + static_assert(foo() == 14); + + constexpr int foo2() { + Child C{}; + try { + throw &C; + } catch (Parent2 *P) { + return P->getFive() + 12; + } + return 0; + } + static_assert(foo2() == 17); + + constexpr int a() { + throw Child{}; + }; + constexpr int foo3() { + try { + a(); + } catch (Parent2 P) { + return P.getFive(); + } + return 0; + } + static_assert(foo3() == 5); + + constexpr int b(Child *C) { + throw C; + }; + + constexpr int foo4() { + Child C{}; + try { + b(&C); + } catch (Parent *P) { + return P->getFive(); + } + return 0; + } + static_assert(foo4() == 5); +} + +namespace Pointer { + static constexpr auto via_const_catch = 2; + static constexpr auto via_childs_get_value = 3; + static constexpr auto via_special_child_catch = 5; + + struct parent { + int value; + explicit constexpr parent(int v) noexcept: value{v} { } + constexpr virtual int get_value() const noexcept { + return value; + } + constexpr virtual ~parent() = default; + }; + + struct modifying_child: parent { + explicit constexpr modifying_child(int v) noexcept: parent{v} { } + constexpr int get_value() const noexcept override { + return value * via_childs_get_value; + } + }; + + struct ordinary_child: parent { + explicit constexpr ordinary_child(int v) noexcept: parent{v} { } + }; + + struct special_child: parent { + explicit constexpr special_child(int v) noexcept: parent{v} { } + }; + + consteval int test(void (*fnc)()) { + int result = 0; + try { + fnc(); + } catch (special_child * sch) { + result = sch->get_value() * via_special_child_catch; + delete sch; + } catch (const special_child * sch) { + result = sch->get_value() * via_special_child_catch * via_const_catch; + delete sch; + } catch (parent * exc) { + result = exc->get_value(); + delete exc; + } catch (const parent * exc) { + result = exc->get_value() * via_const_catch; + delete exc; + } + return result; + } + + constexpr auto r1 = test([] { throw new parent{1}; }); + static_assert(r1 == 1); + + constexpr auto r2 = test([] { throw new modifying_child{3}; }); + static_assert(r2 == 3 * via_childs_get_value); + + constexpr auto r3 = test([] { throw new ordinary_child{5}; }); + static_assert(r3 == 5); + + constexpr auto r4 = test([] { throw new special_child{17}; }); + static_assert(r4 == 17 * via_special_child_catch); +} + +namespace References1 { + class Parent { + public: + int F = 10; + constexpr int getTen() { return F; } + }; + + class Child : public Parent { + public: + int F = 5; + constexpr int getFive() { return F; } + + }; + + constexpr int foo() { + try { + throw Child{}; + } catch (Child &C) { + return C.getFive(); + } + return 0; + } + static_assert(foo() == 5); + + constexpr int nested() { + throw Child{}; + return 1; + }; + constexpr int foo2() { + try { + nested(); + } catch (Child &C) { + return C.getFive(); + } + return 0; + } + static_assert(foo2() == 5); + + constexpr int foo3() { + try { + throw Child{}; + } catch (Parent &P) { + return P.getTen(); + } + return 0; + } + static_assert(foo3() == 10); + + constexpr int foo4() { + try { + nested(); + } catch (Parent &P) { + return P.getTen(); + } + return 0; + } + static_assert(foo4() == 10); + + constexpr int foo5() { + try { + throw 13; + } catch (const int &a) { + return 25; + } + return -1; + } + static_assert(foo5() == 25); + + consteval bool reference_test(const int & ref) { + try { + throw ref; + } catch (const int & exc_ref) { + if (exc_ref != ref) { + return 3; + } else if (&exc_ref != &ref) { + return 2; + } + return 1; + } + } + static_assert(reference_test(10) == 1); + + consteval bool copy_test(const int & ref) { + try { + throw ref; + } catch (const int exc_ref) { + if (exc_ref != ref) { + return 3; + } else if (&exc_ref == &ref) { + return 2; + } + return 1; + } + } + static_assert(copy_test(10) == 1); + + consteval bool conversion_test(const int & ref) { + try { + throw ref; + } catch (const long exc_ref) { + if (exc_ref != ref) { + return 3; + } else if (static_cast<const void *>(&exc_ref) == static_cast<const void *>(&ref)) { + return 2; + } + return 4; + } catch (int) { + return 1; + } + } + static_assert(conversion_test(10) == 1); + + + + +} + +namespace References2 { + static constexpr auto via_const_catch = 2; + static constexpr auto via_childs_get_value = 3; + static constexpr auto via_special_child_catch = 5; + + struct parent { + int value; + explicit constexpr parent(int v) noexcept: value{v} { } + constexpr virtual int get_value() const noexcept { + return value; + } + constexpr virtual ~parent() = default; + }; + + struct modifying_child: parent { + explicit constexpr modifying_child(int v) noexcept: parent{v} { } + constexpr int get_value() const noexcept override { + return value * via_childs_get_value; + } + }; + + struct ordinary_child: parent { + explicit constexpr ordinary_child(int v) noexcept: parent{v} { } + }; + + struct special_child: parent { + explicit constexpr special_child(int v) noexcept: parent{v} { } + }; + + consteval int test(void (*fnc)()) { + int result = 0; + try { + fnc(); + } catch (special_child & sch) { + result = sch.get_value() * via_special_child_catch; + } catch (const special_child & sch) { + result = sch.get_value() * via_special_child_catch * via_const_catch; + } catch (parent & exc) { + result = exc.get_value(); + } catch (const parent & exc) { + result = exc.get_value() * via_const_catch; + } + return result; + } + + constexpr auto r1 = test([] { throw parent{1}; }); + static_assert(r1 == 1); + + constexpr auto r2 = test([] { throw modifying_child{3}; }); + static_assert(r2 == 3 * via_childs_get_value); + + constexpr auto r3 = test([] { throw ordinary_child{5}; }); + static_assert(r3 == 5); + + constexpr auto r4 = test([] { throw special_child{17}; }); + static_assert(r4 == 17 * via_special_child_catch); +} + +namespace Comma { + constexpr int catch_it() { + try { + return (1, throw 2, 3); // expected-warning {{left operand of comma operator has no effect}} + } catch (int v) { + return v; + } + } + static_assert(catch_it() == 2); +} + +namespace Lifetime { + [[noreturn]] constexpr auto create(int v) -> int { + throw v; // expected-note {{uncaught exception of type 'int': '42'}} \ + // expected-note {{uncaught exception of type 'int': '56'}} \ + // expected-note {{uncaught exception of type 'int': '32'}} \ + // expected-note {{uncaught exception of type 'int': '4'}} \ + // expected-note {{uncaught exception of type 'int': '1'}} + } + + constexpr int convert(int x) { + return x; + } + + constexpr auto value1 = convert(create(42)); // expected-error {{must be initialized by a constant expression}} + + + struct wrapper { + int value; + }; + + constexpr auto value2 = wrapper{create(56)}; // expected-error {{must be initialized by a constant expression}} + constexpr auto value3 = wrapper(create(32)); // expected-error {{must be initialized by a constant expression}} + constexpr auto value4 = (1,2,3,create(4)); // expected-error {{must be initialized by a constant expression}} + + constexpr int fnc() { + const auto v = create(1); + return v; + } + + constexpr auto value5 = fnc(); // expected-error {{must be initialized by a constant expression}} +} + +namespace TheWorst { + constexpr int zomg() try { + throw 12; + } catch (int i) { + return 13; + } + + constexpr int c() { + return zomg(); + } + static_assert(c() == 13); + + + struct hanaxception { + int v; + }; + + struct checker { + int value; + constexpr checker(int v) try : value{v} { + if (v > 10) { + throw hanaxception{v}; + } + } catch (const hanaxception h) { + } + }; + + constexpr int test() { + auto c = checker{11}; + return 42; + } + constexpr int constructor_test = test(); + static_assert(constructor_test == 42); +} + +namespace Destructors { + struct F { + constexpr ~F() noexcept(false){ + throw 42; // expected-note {{uncaught exception of type 'int': '42'}} + } + }; + constexpr int test() { + try { + F f; + return 1337; + } catch (int i) { + return i; + } + + return 12; + } + static_assert(test() == 42); + + constexpr int test2() { + F f; + return 1337; + } + static_assert(test2() == 42); // expected-error {{not an integral constant expression}} + +} + +namespace Noexcept { + constexpr void throw_exception_here() noexcept(false) { + throw 42; + } + + constexpr int test() noexcept { + throw_exception_here(); // expected-note {{uncaught exception in noexcept function}} + return 42; + } + constexpr int value = test(); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{in call to}} +} + +namespace Move { + struct foo { + int value; + constexpr foo(int v): value{v} {} + constexpr foo(foo && other): value{other.value + 1} {} + constexpr int get() const { + return value; + } + }; + + consteval int testMove() { + try { + throw std::move(foo{1}); + } catch (const foo & f) { + return f.get(); + } + return 8; + } + static_assert(testMove() == 2); +} + +#if 0 +namespace UncaughtExceptions { + constexpr int foo() { + try { + throw 42; + } catch (...) { + return __builtin_uncaught_exceptions(); + } + return -1; + } + static_assert(foo() == 0); + + struct F{ + constexpr ~F() { + if (__builtin_uncaught_exceptions() != 0) { + __builtin_abort(); // expected-note {{subexpression not valid in a constant expression}} + } + } + }; + constexpr int foo2() { + + try { + F f; // expected-note {{in call to}} + throw 42; + } catch (...) { + return 0; + } + + return -1; + } + static_assert(foo2() == 0); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} + + constexpr int foo3() { + try { + throw 42; + } catch (int) { + return __builtin_uncaught_exceptions(); + } + return -1; + } + static_assert(foo3() == 0); +} +#endif + +namespace Uncaught2 { + class E : std::exception { + constexpr const char *what() const noexcept { + return "SOME EXCEPTION WHADDAYAKNOW"; + } + }; + constexpr int foo() { + // FIXME: Call what() instead(?) + throw E(); // expected-note {{uncaught exception of type 'E': '&E()'}} + return 1; + } + static_assert(foo() == 1); // expected-error {{static assertion expression is not an integral constant expression}} + + +} + +namespace UnusualTypes { + constexpr int foo1() { + throw (int[]){1,2,3}; // expected-note {{uncaught exception of type 'int *': '&(int[3]){1, 2, 3}[0]'}} + return 1; + } + static_assert(foo1() == 1); // expected-error {{not an integral constant expression}} + + + constexpr int foo2() { + throw 1i; // expected-note {{uncaught exception of type '_Complex int': '&1i'}} + return 1; + } + static_assert(foo2() == 1); // expected-error {{not an integral constant expression}} +} + +namespace VirtCallOnException { + struct A { + virtual constexpr int getNumber()const { return 10; } + }; + struct B : A {}; + struct C : B { + constexpr int getNumber() const override { + return 100; + } + }; + + constexpr int foo() { + try { + throw C{}; + } catch (A& a) { + return a.getNumber(); + } + return 1; + } + static_assert(foo() == 100); + +} + +namespace VirtCall { + struct A { + virtual constexpr int getNumber()const { return 10; } + }; + struct B : A {}; + struct C : B { + constexpr int getNumber() const override { + return 100; + } + }; + + constexpr void nested() { + throw C{}; + } + + struct F { + constexpr virtual int foo() { + throw C{}; + return 0; + } + }; + + constexpr int foo() { + try { + F f; + f.foo(); + } catch (A& a) { + return a.getNumber(); + } + return 1; + } + static_assert(foo() == 100); +} + +namespace Variadic { + constexpr void variadic(...) { + throw 123; + } + constexpr int foo() { + try { + variadic(1,2,3,4); + } catch (int a) { + return a; + } + return 1; + } + static_assert(foo() == 123); +} + +namespace StmtExpr { + constexpr int foo() { + try { + return ({ + int a = 123; + throw 100; + 12; + }) + == 12; + } catch (int) { + return 200; + } + + return -1; + } + static_assert(foo() == 200); +} + +namespace TryBody { + constexpr int t() { + throw 100; + }; + + constexpr int test() try { + t(); + return 1; + } catch (...) { + + return 20; + } + static_assert(test() == 20); +} + +namespace ReThrow { + constexpr int t() { + throw 100; + }; + + constexpr int test() { + try { + t(); + return 1; + } catch (...) { + throw; + return -4; + } + return -10; + } + constexpr int test2() { + try { + test(); + } catch (int a) { + return a; + } + return -1; + } + static_assert(test2() == 100); + + + constexpr int test3() { + throw; // expected-note {{rethrow with no active exception}} + } + static_assert(test3() == 100); // expected-error {{static assertion expression is not an integral constant expression}} \ + // expected-note {{in call to}} + + constexpr int test4() { + try { + throw 100; // expected-note {{uncaught exception of type 'int': '100'}} + } catch(int) { + throw; + } + } + static_assert(test4() == 100); // expected-error {{static assertion expression is not an integral constant expression}} +} + +namespace PointerInThrownValue { + /// Used to crash because of lifetime issues between the Pointer + /// saved in ThrownValue and the InterpState. + struct P {}; + struct C : P{ + constexpr C() {} + }; + constexpr int foo() { + + try { + } catch (int e) { + } + + try { + auto thrower = []() { throw C(); }; + thrower(); + } catch (const P&) { + return 100; + } + + return -1; + } + static_assert(foo() == 100); +} + +namespace CatchAfterCallWithExceptionAlreadySet { + struct P {}; + struct C : P{ + constexpr C() {} + }; + constexpr int foo() { + + try { + throw 42; + } catch (int e) { + } + try { + auto thrower = []() { throw C(); }; + thrower(); + } catch (const P&) { + return 100; + } + + return -1; + } +} + +namespace PtrIntoLocal { + constexpr int foo() { + try { + throw "help"; + } catch (const char *p) { + return (int)p[2]; + } + return -1; + } + static_assert(foo() == 'l'); +} + +namespace NestedTry { + constexpr int func(int n, int m) { + return 10; + } + constexpr int foo() { + try { + int a; + a = 10; + try { + int b; + b = 20; + func(10, ({throw 30; 20;})); + throw b; + + + } catch(...) { + throw; + } + } catch(int x) { + return x; + } + return -1; + } + static_assert(foo() == 30); + +} + +namespace ThrowWithActiveException { + struct S { + constexpr ~S() noexcept(false) { + throw 2; // expected-note {{throw while another exception is already active}} + } + }; + + constexpr int f() { + try { + S s; // expected-note {{in call to 's.~S()'}} + throw 1; // expected-note {{currently active exception thrown here}} + } catch (...) { + } + return 0; + } + static_assert(f() == 0); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} +} + +namespace DifferentCastAfterRetgrow { + struct A {}; + struct B : A {}; + struct C : B {}; + + constexpr void a() { + throw C{}; + }; + constexpr void b() { + try { + a(); + } catch (A) { + throw; + } + } + constexpr int c() { + try { + b(); + } catch (B) { + return 10; + } catch (...) { + return -2; + } + return -1; + } + static_assert(c() == 10); +} diff --git a/clang/test/AST/ByteCode/invalid.cpp b/clang/test/AST/ByteCode/invalid.cpp index e79b698a32719..f70f843569c03 100644 --- a/clang/test/AST/ByteCode/invalid.cpp +++ b/clang/test/AST/ByteCode/invalid.cpp @@ -1,31 +1,5 @@ -// RUN: %clang_cc1 -triple x86_64 -fcxx-exceptions -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s -// RUN: %clang_cc1 -triple x86_64 -fcxx-exceptions -std=c++20 -verify=ref,both %s - -namespace Throw { - - constexpr int ConditionalThrow(bool t) { - if (t) - throw 4; // both-note {{subexpression not valid in a constant expression}} - - return 0; - } - - static_assert(ConditionalThrow(false) == 0, ""); - static_assert(ConditionalThrow(true) == 0, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to 'ConditionalThrow(true)'}} - - constexpr int Throw() { // both-error {{never produces a constant expression}} - throw 5; // both-note {{subexpression not valid in a constant expression}} - return 0; - } - - constexpr int NoSubExpr() { // both-error {{never produces a constant expression}} - throw; // both-note 2{{subexpression not valid}} - return 0; - } - static_assert(NoSubExpr() == 0, ""); // both-error {{not an integral constant expression}} \ - // both-note {{in call to}} -} +// RUN: %clang_cc1 -triple x86_64 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s +// RUN: %clang_cc1 -triple x86_64 -std=c++20 -verify=ref,both %s namespace Asm { constexpr int ConditionalAsm(bool t) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
