[clang] [clang][bytecode] Support compile-time exceptions (PR #189410)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 2 01:09:42 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/189410
>From eb2c920fff67e16b09aa2b9b44e3b4968f44b4db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
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 | 246 ++-
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 | 128 ++
clang/lib/AST/ByteCode/Exceptions.h | 52 +
clang/lib/AST/ByteCode/Function.cpp | 11 +
clang/lib/AST/ByteCode/Function.h | 12 +-
clang/lib/AST/ByteCode/Interp.cpp | 165 +-
clang/lib/AST/ByteCode/Interp.h | 88 +-
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 | 27 +
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 | 1501 +++++++++++++++++
clang/test/AST/ByteCode/invalid.cpp | 30 +-
26 files changed, 2280 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 0aca1f75428f8..8c0839d12a23f 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -417,6 +417,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 2b2e6be3bdce2..5d947068c7cd3 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
@@ -84,7 +84,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 4174a26b80464..f0dcab6c789ed 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
@@ -83,6 +83,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;
@@ -98,6 +106,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 42859942d2925..6438e6cdc67e8 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -60,6 +60,30 @@ static bool exceedsArraySizeLimit(const LangOptions &LangOpts,
return Limit != 0 && NumElems > Limit;
}
+[[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;
+}
+
+const CXXConstructorDecl *findCopyCtor(const CXXRecordDecl *RD,
+ unsigned ExpectedQualifiers) {
+ if (!RD)
+ return nullptr;
+
+ unsigned Qualifiers;
+ for (CXXConstructorDecl *Ctor : RD->ctors()) {
+ if (Ctor->isCopyConstructor(Qualifiers) && Qualifiers == ExpectedQualifiers)
+ return Ctor;
+ }
+
+ return nullptr;
+}
+
/// Scope chain managing the variable lifetimes.
template <class Emitter> class VariableScope {
public:
@@ -3842,10 +3866,146 @@ 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(),
+ /*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;
+
+ // Copy the exception object to the local variable, either via copy ctor
+ // or memcpy.
+ if (const auto *Ctor = findCopyCtor(CatchType->getAsCXXRecordDecl(),
+ CatchType.getCVRQualifiers())) {
+ const Function *Func = getFunction(Ctor);
+ if (!Func)
+ return false;
+ if (!this->emitCall(Func, 0, Handler))
+ return false;
+ } else {
+ 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>
@@ -7419,12 +7579,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 for (auto x : {1, 2}) {}
///
/// This is not a loop from an AST perspective at all since it has already
@@ -7517,11 +7671,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>
@@ -7757,11 +7909,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>
@@ -7832,7 +7994,11 @@ bool Compiler<Emitter>::compileDestructor(const CXXDestructorDecl *Dtor) {
if (!this->emitMarkDestroyed(Dtor))
return false;
- 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>
@@ -7875,14 +8041,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) {
@@ -9009,6 +9193,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 56a4d098640c3..511ebc798e327 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -475,6 +475,9 @@ class Compiler final : 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 110b5073ecf1f..d10e62e5b1e6c 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -34,6 +34,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 262f5b47f87c2..f1d5de3ae6225 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -48,6 +48,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 22d0828df423f..73dc63b093ced 100644
--- a/clang/lib/AST/ByteCode/Disasm.cpp
+++ b/clang/lib/AST/ByteCode/Disasm.cpp
@@ -161,6 +161,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 2f9507c04628c..324e1ed0870c3 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.h
+++ b/clang/lib/AST/ByteCode/EvalEmitter.h
@@ -77,6 +77,16 @@ class EvalEmitter : public SourceMapper {
EvalEmitter(Context &Ctx, Program &P, Expr::EvalStatus &Status,
InterpStack &Stk, FrameAllocator &FrameAlloc);
+ 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..71b3beb401dbd
--- /dev/null
+++ b/clang/lib/AST/ByteCode/Exceptions.cpp
@@ -0,0 +1,128 @@
+//===-------------------------- 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"
+#include "clang/AST/CXXInheritance.h"
+
+using namespace clang;
+using namespace clang::interp;
+
+static bool isPointerOrMemberPointerType(const Type *T) {
+ return T->isPointerType() || T->isMemberPointerType();
+}
+static bool isPointerOrMemberPointerType(QualType T) {
+ return isPointerOrMemberPointerType(T.getTypePtr());
+}
+
+bool ExceptionTableEntry::canCatch(const Type *ThrowType,
+ const ASTContext &ASTCtx) const {
+ const Type *CatchType = this->CatchType;
+ // "A handler is a match for an exception object of type E if ..."
+
+ // "The handler is of type cv T or cv T& and E and T are the same type
+ // (ignoring the top-level cv-qualifiers)"
+ if (!CatchType || ASTContext::hasSameType(CatchType, ThrowType) ||
+ (CatchType->isReferenceType() &&
+ ASTContext::hasSameType(CatchType->getPointeeType().getTypePtr(),
+ ThrowType)))
+ return true;
+
+ assert(CatchType);
+
+ // "the handler is of type cv T or const T& where T is a pointer or
+ // pointer-to-member type and E is std::nullptr_t"
+ if (ThrowType->isNullPtrType()) {
+ if (isPointerOrMemberPointerType(CatchType))
+ return true;
+
+ if (CatchType->isReferenceType() &&
+ (CatchType->getPointeeType()->isPointerType() ||
+ CatchType->getPointeeType()->isMemberPointerType()))
+ return true;
+ }
+
+ // void* can catch all thrown pointer types.
+ if (ThrowType->isPointerType() && CatchType->isVoidPointerType())
+ return true;
+
+ // "the handler is of type cv T or const T& where T is a pointer or
+ // pointer-to-member type and E is a pointer or pointer-to-member type that
+ // can be converted to T by one or more of ..."
+ if ((isPointerOrMemberPointerType(CatchType) ||
+ (CatchType->isReferenceType() &&
+ isPointerOrMemberPointerType(CatchType->getPointeeType()))) &&
+ isPointerOrMemberPointerType(ThrowType)) {
+
+ // "a function pointer conversion"
+ if (CatchType->isFunctionPointerType() &&
+ ThrowType->isFunctionPointerType()) {
+ const auto *FuncT =
+ CatchType->getPointeeType()->castAs<FunctionProtoType>();
+ const auto *FuncE =
+ ThrowType->getPointeeType()->castAs<FunctionProtoType>();
+
+ // We can catch a noexcept function as non-noexcept, but not the other way
+ // around.
+ if (FuncT->hasNoexceptExceptionSpec() &&
+ !FuncE->hasNoexceptExceptionSpec())
+ return false;
+ // Both being noexcept is also fine.
+ return true;
+ }
+
+ // "a qualification conversion"
+ if (CatchType->isPointerType() && ThrowType->isPointerType()) {
+ QualType PointeeT = CatchType->getPointeeType();
+ QualType PointeeE = ThrowType->getPointeeType();
+
+ if (ASTContext::hasSameType(PointeeT, PointeeE))
+ return true;
+
+ // We can catch T* as const T*, not not the other way around.
+ if (!PointeeT.isConstQualified() && PointeeE.isConstQualified())
+ return false;
+ if (ASTCtx.hasSimilarType(PointeeT, PointeeE))
+ return true;
+ }
+ }
+
+ // "the handler is of type cv T or cv T& and T is an unambiguous public base
+ // class of E"
+ if (CatchType->isReferenceType())
+ CatchType = CatchType->getPointeeType().getTypePtr();
+
+ // T* can only catch T*, not T.
+ if (CatchType->isPointerType() && ThrowType->isPointerType()) {
+ CatchType = CatchType->getPointeeType().getTypePtr();
+ ThrowType = ThrowType->getPointeeType().getTypePtr();
+ }
+
+ if (CatchType->isRecordType() && ThrowType->isRecordType()) {
+ const CXXRecordDecl *CatchDecl = CatchType->getAsCXXRecordDecl();
+ const CXXRecordDecl *ThrowDecl = ThrowType->getAsCXXRecordDecl();
+ assert(CatchDecl);
+ assert(ThrowDecl);
+
+ if (CatchDecl == ThrowDecl)
+ return true;
+
+ CXXBasePaths Paths;
+ if (ThrowDecl->isDerivedFrom(CatchDecl, Paths)) {
+ if (Paths.isAmbiguous(ASTCtx.getCanonicalTagType(CatchDecl)))
+ return false;
+
+ if (Paths.front().Access != AS_public)
+ return false;
+
+ 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..8987b330a8889
--- /dev/null
+++ b/clang/lib/AST/ByteCode/Exceptions.h
@@ -0,0 +1,52 @@
+//===----------------------- 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;
+class ASTContext;
+
+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 ASTContext &ASTCtx) 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 38b0ab7dfc7d6..c0a91debf7c69 100644
--- a/clang/lib/AST/ByteCode/Function.cpp
+++ b/clang/lib/AST/ByteCode/Function.cpp
@@ -66,3 +66,14 @@ SourceInfo Function::getSource(CodePtr PC) const {
unsigned Offset = PC - getCodeBegin();
return SrcMap.findSourceForOffset(Offset);
}
+
+std::optional<ExceptionTableEntry>
+Function::findCatchHandler(unsigned CodeOffset, const Type *Ty,
+ const ASTContext &ASTCtx) const {
+ for (const auto &E : ExceptionTable) {
+ if (CodeOffset >= E.CodeStart && CodeOffset <= E.CodeEnd &&
+ E.canCatch(Ty, ASTCtx))
+ return E;
+ }
+ return std::nullopt;
+}
diff --git a/clang/lib/AST/ByteCode/Function.h b/clang/lib/AST/ByteCode/Function.h
index 6215e1d684004..e662b5ba873f6 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"
@@ -253,6 +254,10 @@ class Function final {
return ArgSize - (align(primSize(PT_Ptr)) * (hasThisPointer() + hasRVO()));
}
+ std::optional<ExceptionTableEntry>
+ findCatchHandler(unsigned CodeOffset, const Type *Ty,
+ const ASTContext &ASTCtx) const;
+
private:
/// Construct a function representing an actual function.
Function(FunctionDeclTy Source, unsigned ArgSize,
@@ -262,13 +267,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;
}
@@ -297,6 +304,7 @@ class Function final {
unsigned ArgSize;
/// Function Kind.
FunctionKind Kind;
+ 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 25605ba04c7bd..78c2f8a8ccfcb 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -275,6 +275,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);
@@ -2096,6 +2115,142 @@ 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,
+ S.getASTContext());
+
+ 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()) {
@@ -2134,6 +2289,10 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
InterpStateCCOverride CCOverride(S, Func->isImmediate());
bool Success = Interpret(S);
S.resetCurrentFrame();
+
+ if (Success && S.ThrownValue && !S.ThrownValue->Caught)
+ return catchException(S, OpPC);
+
return Success;
}
@@ -2223,6 +2382,10 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
S.InitializingPtrs.pop_back();
S.resetCurrentFrame();
+
+ if (Success && S.ThrownValue && !S.ThrownValue->Caught)
+ return catchException(S, OpPC);
+
return Success;
}
@@ -3803,7 +3966,7 @@ bool TrivialCopy(InterpState &S, CodePtr OpPC, bool Activate,
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 7592d9e3c68ea..08fb37bc20f4d 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -84,6 +84,8 @@ bool diagnoseArrayIndex(InterpState &S, CodePtr OpPC, const APSInt &Index,
std::optional<uint64_t> NumElems = std::nullopt,
bool IsArray = true);
+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);
@@ -147,6 +149,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 {
@@ -313,6 +318,83 @@ 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;
+ }
+
+ if constexpr (std::is_same_v<T, MemberPointer>) {
+ PrimType BlockPrimT = S.ThrownValue->B->getDescriptor()->getPrimType();
+ if (BlockPrimT == PT_Ptr) {
+ // This should only happen for null pointers.
+ S.Stk.push<T>();
+ return true;
+ } else if (BlockPrimT != PT_MemberPtr)
+ return false;
+ }
+
+ assert(S.ThrownValue->B->getDescriptor()->getPrimType() == Name);
+ 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
//===----------------------------------------------------------------------===//
@@ -2427,7 +2509,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;
}
@@ -2438,7 +2521,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 acc9c337bfb1b..a188a3e97f0ad 100644
--- a/clang/lib/AST/ByteCode/InterpFrame.h
+++ b/clang/lib/AST/ByteCode/InterpFrame.h
@@ -79,10 +79,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 {
@@ -216,10 +214,8 @@ class InterpFrame final : public Frame {
CodePtr RetPC;
/// 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
/// The size of all the arguments.
const unsigned ArgSize;
/// Depth of this frame.
diff --git a/clang/lib/AST/ByteCode/InterpStack.h b/clang/lib/AST/ByteCode/InterpStack.h
index ce2ed78730717..d23c588a782f7 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 8592998cc721a..0f85ac0e63b82 100644
--- a/clang/lib/AST/ByteCode/InterpState.cpp
+++ b/clang/lib/AST/ByteCode/InterpState.cpp
@@ -69,6 +69,14 @@ bool InterpState::inConstantContext() const {
InterpState::~InterpState() {
assert(Current->isBottomFrame());
+ // Invoke the dtor func of the allocated exception object block.
+ if (ThrownValue) {
+ Block *B = ThrownValue->B;
+ if (B && B->isInitialized())
+ deallocate(B);
+ ThrownValue = nullptr;
+ }
+
while (DeadBlocks) {
DeadBlock *Next = DeadBlocks->Next;
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index eb290584285db..e1d1bd3289e5a 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 "FrameAllocator.h"
#include "Function.h"
@@ -262,10 +263,12 @@ class InterpState final : public State {
const bool InfiniteSteps = false;
/// ID identifying this evaluation.
const unsigned EvalID;
+ unsigned ThrowTrapStackSize = 0;
unsigned StringID = 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 8b92654adfd6f..2382d78f964a6 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -74,6 +74,7 @@ def ArgEnumDecl : ArgType { let Name = "const EnumDecl *"; }
def ArgTypePtr : ArgType { let Name = "const Type *"; }
def ArgDeclOrExpr : ArgType { let Name = "DeclOrExpr"; }
def ArgCtorDecl : ArgType { let Name = "const CXXConstructorDecl *"; }
+def ArgOptPrimType : ArgType { let Name = "OptPrimType"; }
//===----------------------------------------------------------------------===//
// Classes of types instructions operate on.
@@ -231,6 +232,32 @@ 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 : Opcode {
+ let Types = [AllTypeClass];
+ let HasGroup = 1;
+ let NeedsOpPC = 0;
+}
+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 dd42b291b0666..91d71b421ce65 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 c703151f78772..5bb1b9fa5a946 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -76,6 +76,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 c5ade52a66b40..4f6cc82cfbb5a 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 e011aef22031b..4f9524d67998d 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..3198d754f6c90
--- /dev/null
+++ b/clang/test/AST/ByteCode/exceptions.cpp
@@ -0,0 +1,1501 @@
+// 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());
+
+
+ constexpr int reference() {
+ try {
+ throw nullptr;
+ } catch (const int *&a) {
+ return 20;
+ }
+ return -1;
+ }
+ static_assert(reference() == 20);
+}
+
+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);
+
+ constexpr int doublePointer() {
+ auto s = Child{};
+ auto *ss = &s;
+
+ try {
+ throw &ss;;
+ } catch (Parent**) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+ return -1;
+ }
+ static_assert(doublePointer() == 1);
+
+
+
+ class Base {};
+ class S : private Base {};
+ constexpr int foo5() {
+ try {
+ throw S{}; // expected-note {{uncaught exception of type 'S': '&S{}'}}
+ } catch (Base) {
+ return 10;
+ }
+ return -1;
+ }
+ static_assert(foo5() == -1); // expected-error {{not an integral constant expression}}
+
+ constexpr int foo6() {
+ auto s = Child{};
+ try {
+ throw &s;
+ } catch (Child*) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo6() == 2);
+
+ constexpr int foo7() {
+ try {
+ throw Child{};
+ } catch (Child*) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo7() == 1);
+
+ constexpr int foo8() {
+ auto s = Child{};
+ try {
+ throw &s;
+ } catch (Parent&) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo8() == 1);
+
+ constexpr int foo9() {
+ auto s = Child{};
+ try {
+ throw (Child&)s;
+ } catch (Parent&) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo9() == 2);
+
+ constexpr int foo10() {
+ auto s = Child{};
+ try {
+ throw (Child&)s;
+ } catch (Parent) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo10() == 2);
+
+ constexpr int foo11() {
+ auto s = Child{};
+ try {
+ throw (Child&&)s;
+ } catch (Parent2) {
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo11() == 2);
+
+
+ constexpr int foo12() {
+ auto s = Child{};
+ try {
+ throw s;
+ } catch (Parent&&) { // expected-error {{cannot catch exceptions by rvalue reference}}
+ return 2;
+ } catch(...) {
+ return 1;
+ }
+
+ return -1;
+ }
+ static_assert(foo12() == 2);
+}
+
+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);
+
+ constexpr int pointeeCatch() {
+ int x = 10;
+ try {
+ throw &x; // expected-note {{uncaught exception of type 'int *': '&x'}}
+ } catch (int ) {
+ return 10;
+ }
+ return 1;
+ }
+
+ static_assert(pointeeCatch() == 1); // expected-error {{not an integral constant expression}}
+}
+
+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);
+}
+
+namespace MemberPointers {
+ struct S {
+ int m, n;
+ };
+ // constexpr int S::*pm = &S::m;
+ constexpr int S::* foo() {
+ try {
+ throw &S::m;
+ } catch (int S::* pm) {
+ return pm;
+ }
+ return nullptr;
+ }
+ static_assert(foo() == &S::m);
+
+ constexpr int S::* foo2() {
+ try {
+ throw &S::m;
+ } catch (int S::* &pm) {
+ return pm;
+ }
+ return nullptr;
+ }
+ static_assert(foo2() == &S::m);
+
+ /// Here, we catch the null pointer as a reference-to-member-pointer.
+ /// This works in regular runtime code. However, there is no proper point of conversion
+ /// from the null pointer to a member pointer.
+ /// We then eventually reject the function when trying to load the member pointer in the
+ /// return statement.
+ /// This matches GCC's behavior for compile-time exceptions.
+ constexpr int S::* foo3() {
+ try {
+ throw nullptr;
+ } catch (int S::* &pm) {
+ return pm;
+ }
+ return nullptr;
+ }
+ static_assert(foo3() == &S::m); // expected-error {{not an integral constant expression}}
+}
+
+namespace FunctionPointers {
+#define fold(x) (__builtin_constant_p(x) ? (x) : (x))
+ extern int zomg();
+
+ constexpr int foo() {
+ try {
+ throw &zomg;
+ } catch (int (*)()) {
+ return 2;
+ } catch (const int ) {
+ return 0;
+ }
+ return -1;
+ }
+ static_assert(foo() == 2);
+
+ constexpr int foo2() {
+ try {
+ throw &zomg;
+ } catch (void* p) {
+ return p == fold((void*)&zomg);
+ }
+ return -1;
+ }
+ static_assert(foo2() == 1);
+
+
+ extern int zomg2() noexcept;
+ constexpr int foo3() {
+ try {
+ throw &zomg;
+ } catch ( int (*)()) {
+ return 13;
+ }
+ return -1;
+ }
+ static_assert(foo3() == 13);
+
+ constexpr int foo4() {
+ try {
+ throw &zomg2;
+ } catch ( int (*)() noexcept) {
+ return 13;
+ }
+ return -1;
+ }
+ static_assert(foo4() == 13);
+
+ constexpr int foo5() {
+ try {
+ throw &zomg; // expected-note {{uncaught exception of type 'int (*)()': '&zomg'}}
+ } catch ( int (*) () noexcept ) {
+ return 13;
+ }
+ return -1;
+ }
+ static_assert(foo5() == 0); // expected-error {{not an integral constant expression}}
+}
+
+namespace CopCtors {
+ struct S {
+ int &ConstCC;
+ int &CC;
+
+ constexpr S(int &CC, int &ConstCC) : CC(CC), ConstCC(ConstCC) {}
+
+ constexpr S(const S& s) :CC(s.CC), ConstCC(s.ConstCC) {
+ ++ConstCC;
+ }
+ constexpr S(S& s) :CC(s.CC), ConstCC(s.ConstCC) {
+ ++CC;
+ }
+ };
+
+ constexpr int foo() {
+ int CopyCtorCalled = 0;
+ int ConstCopyCtorCalled = 0;
+ try {
+ throw S(CopyCtorCalled, ConstCopyCtorCalled);
+ } catch (S) {
+ }
+ return CopyCtorCalled + (ConstCopyCtorCalled * 10);
+ }
+ static_assert(foo() == 1);
+
+ constexpr int foo2() {
+ int CopyCtorCalled = 0;
+ int ConstCopyCtorCalled = 0;
+ auto M = S(CopyCtorCalled, ConstCopyCtorCalled);
+ try {
+ throw M;
+ } catch (S) {
+ }
+ return CopyCtorCalled + (ConstCopyCtorCalled * 10);
+ }
+ static_assert(foo2() == 2);
+
+ constexpr int foo3() {
+ int CopyCtorCalled = 0;
+ int ConstCopyCtorCalled = 0;
+
+ const auto M = S(CopyCtorCalled, ConstCopyCtorCalled);
+ try {
+ throw M;
+ } catch (S) {
+ }
+
+ return CopyCtorCalled + (ConstCopyCtorCalled * 10);
+ }
+ static_assert(foo3() == 11);
+
+
+}
+
+namespace QualificationConversion {
+ constexpr int foo() {
+ int a = 100;
+ try {
+ throw &a;
+ } catch ( const int *k) {
+ return *k;
+ }
+ return -1;
+ }
+ static_assert(foo() == 100);
+
+ constexpr int foo2() {
+ const int a = 100;
+ try {
+ throw &a;
+ } catch ( const int *k) {
+ return *k;
+ }
+ return -1;
+ }
+ static_assert(foo2() == 100);
+
+
+ constexpr int foo3() {
+ const int a = 100;
+ try {
+ throw &a; // expected-note {{uncaught exception of type 'const int *': '&a'}}
+ } catch (int *k) {
+ return *k;
+ }
+ return -1;
+ }
+ static_assert(foo3() == -1); // expected-error {{not an integral constant expression}}
+
+}
diff --git a/clang/test/AST/ByteCode/invalid.cpp b/clang/test/AST/ByteCode/invalid.cpp
index cf859eb75dd90..1c172302461e8 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) {
More information about the cfe-commits
mailing list