This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 331664cd [FIX] Make object construction exception-safe (#650)
331664cd is described below
commit 331664cd52342c53fbe4286072547184192cdfdf
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Jul 4 22:40:05 2026 +0800
[FIX] Make object construction exception-safe (#650)
This change makes SimpleObjAllocator strongly exception-safe when
placement construction throws.
It adds an allocator-local allocation guard to both ordinary object and
variable-sized array creation. The guard frees the matching aligned
allocation during exception unwinding and is disarmed after successful
construction. A focused object test verifies constructor exceptions
propagate through make_object.
---
include/tvm/ffi/memory.h | 27 ++++++++++++++++++++++++++-
tests/cpp/test_object.cc | 14 ++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/include/tvm/ffi/memory.h b/include/tvm/ffi/memory.h
index b4fabed5..50ed6954 100644
--- a/include/tvm/ffi/memory.h
+++ b/include/tvm/ffi/memory.h
@@ -83,7 +83,7 @@ TVM_FFI_INLINE void* AlignedAlloc(size_t size) {
* \brief Free aligned memory.
* \param data The pointer to the memory to free.
*/
-TVM_FFI_INLINE void AlignedFree(void* data) {
+TVM_FFI_INLINE void AlignedFree(void* data) noexcept {
#ifdef _MSC_VER
// MSVC have to use _aligned_free
_aligned_free(data);
@@ -149,6 +149,27 @@ class ObjAllocatorBase {
// Simple allocator that uses new/delete.
class SimpleObjAllocator : public ObjAllocatorBase<SimpleObjAllocator> {
+ private:
+ /*! \brief Guard a simple-allocator allocation until ownership is
transferred to an object. */
+ class AllocGuard {
+ public:
+ explicit AllocGuard(void* data) noexcept : data_(data) {}
+ AllocGuard(const AllocGuard&) = delete;
+ AllocGuard& operator=(const AllocGuard&) = delete;
+
+ ~AllocGuard() noexcept {
+ if (data_ != nullptr) {
+ AlignedFree(data_);
+ }
+ }
+
+ /*! \brief Disarm the guard after successful in-place construction. */
+ void Release() noexcept { data_ = nullptr; }
+
+ private:
+ void* data_;
+ };
+
public:
template <typename T>
class Handler {
@@ -169,7 +190,9 @@ class SimpleObjAllocator : public
ObjAllocatorBase<SimpleObjAllocator> {
// We are fine here as we captured the right deleter during construction.
// This is also the right way to get storage type for an object pool.
void* data = AlignedAlloc<alignof(T)>(sizeof(T));
+ AllocGuard alloc_guard(data);
new (data) T(std::forward<Args>(args)...);
+ alloc_guard.Release();
return reinterpret_cast<T*>(data);
}
@@ -221,7 +244,9 @@ class SimpleObjAllocator : public
ObjAllocatorBase<SimpleObjAllocator> {
// C++ standard always guarantees that alignof operator returns a power
of 2
size_t aligned_size = (size + (align - 1)) & ~(align - 1);
void* data = AlignedAlloc<align>(aligned_size);
+ AllocGuard alloc_guard(data);
new (data) ArrayType(std::forward<Args>(args)...);
+ alloc_guard.Release();
return reinterpret_cast<ArrayType*>(data);
}
diff --git a/tests/cpp/test_object.cc b/tests/cpp/test_object.cc
index 25a8d8da..a597e425 100644
--- a/tests/cpp/test_object.cc
+++ b/tests/cpp/test_object.cc
@@ -28,6 +28,8 @@
#include <tvm/ffi/object.h>
#include <tvm/ffi/optional.h>
+#include <stdexcept>
+
#include "./testing_object.h"
namespace tvm {
@@ -119,6 +121,14 @@ class LeafObject : public CRTPObject<LeafObject> {
static constexpr const char* _type_key = "test.CRTPLeaf";
};
+class ThrowingConstructorObject : public Object {
+ public:
+ ThrowingConstructorObject() { throw std::runtime_error("constructor
failed"); }
+
+ TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.ThrowingConstructorObject",
ThrowingConstructorObject,
+ Object);
+};
+
TEST(Object, RefCounter) {
ObjectPtr<TIntObj> a = make_object<TIntObj>(11);
ObjectPtr<TIntObj> b = a;
@@ -142,6 +152,10 @@ TEST(Object, RefCounter) {
EXPECT_EQ(c->value, 11);
}
+TEST(Object, MakeObjectPropagatesConstructorException) {
+ EXPECT_THROW(make_object<ThrowingConstructorObject>(), std::runtime_error);
+}
+
TEST(Object, TypeInfo) {
const TypeInfo* info = TVMFFIGetTypeInfo(TIntObj::RuntimeTypeIndex());
EXPECT_TRUE(info != nullptr);