From 0091c4e12b3e25ecdf9c0da091e32739d7907d10 Mon Sep 17 00:00:00 2001
From: Max Kellermann <mk@cm4all.com>
Date: Mon, 1 Mar 2021 23:01:37 +0100
Subject: [PATCH] util/Exception: add FindNested()

---
 src/util/Exception.hxx      | 21 +++++++++++
 test/util/TestException.cxx | 69 +++++++++++++++++++++++++++++++++++++
 2 files changed, 90 insertions(+)

diff --git a/src/util/Exception.hxx b/src/util/Exception.hxx
index 390f5763f..3f131a260 100644
--- a/src/util/Exception.hxx
+++ b/src/util/Exception.hxx
@@ -83,6 +83,27 @@ NestException(std::exception_ptr ep, T &&t) noexcept
 	}
 }
 
+/**
+ * Find an instance of #T in the nested exception chain, and return a
+ * pointer.  Returns nullptr if no such instance was found.
+ */
+template<typename T>
+[[gnu::pure]]
+inline const T *
+FindNested(std::exception_ptr ep) noexcept
+{
+	try {
+		std::rethrow_exception(ep);
+	} catch (const T &t) {
+		return &t;
+	} catch (const std::nested_exception &ne) {
+		return FindNested<T>(ne.nested_ptr());
+	} catch (...) {
+	}
+
+	return nullptr;
+}
+
 /**
  * Find an instance of #T in the nested exception chain, and rethrow
  * it.  Does nothing if no such instance was found.
diff --git a/test/util/TestException.cxx b/test/util/TestException.cxx
index be4e557e5..947b86f91 100644
--- a/test/util/TestException.cxx
+++ b/test/util/TestException.cxx
@@ -50,6 +50,75 @@ TEST(ExceptionTest, DerivedError)
 	ASSERT_EQ(GetFullMessage(std::make_exception_ptr(DerivedError("Foo"))), "Foo");
 }
 
+TEST(ExceptionTest, FindNestedDirect)
+{
+	struct Foo {};
+	struct Bar {};
+	struct Derived : Foo {};
+
+	try {
+		throw Foo{};
+	} catch (...) {
+		EXPECT_NE(FindNested<Foo>(std::current_exception()),
+			  nullptr);
+	}
+
+	try {
+		throw Bar{};
+	} catch (...) {
+		EXPECT_EQ(FindNested<Foo>(std::current_exception()),
+			  nullptr);
+	}
+
+	try {
+		throw Derived{};
+	} catch (...) {
+		EXPECT_NE(FindNested<Foo>(std::current_exception()),
+			  nullptr);
+	}
+}
+
+TEST(ExceptionTest, FindNestedIndirect)
+{
+	struct Foo {};
+	struct Bar {};
+	struct Derived : Foo {};
+	struct Outer {};
+
+	try {
+		throw Foo{};
+	} catch (...) {
+		try {
+			std::throw_with_nested(Outer{});
+		} catch (...) {
+			EXPECT_NE(FindNested<Foo>(std::current_exception()),
+				  nullptr);
+		}
+	}
+
+	try {
+		throw Bar{};
+	} catch (...) {
+		try {
+			std::throw_with_nested(Outer{});
+		} catch (...) {
+			EXPECT_EQ(FindNested<Foo>(std::current_exception()),
+				  nullptr);
+		}
+	}
+
+	try {
+		throw Derived{};
+	} catch (...) {
+		try {
+			std::throw_with_nested(Outer{});
+		} catch (...) {
+			EXPECT_NE(FindNested<Foo>(std::current_exception()),
+				  nullptr);
+		}
+	}
+}
+
 template<typename T>
 static bool
 CheckFindRetrowNested(std::exception_ptr e) noexcept