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);

Reply via email to