Skip to content
Merged

Dev #390

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Agent Instructions

## Rules

- NEVER add an `#include` header to any file. To control header include order, edit `rice/rice.hpp`.
- NEVER run `ruby lib/rice/make_rice_headers.rb`. The `include/` folder is autogenerated by a GitHub hook.

## Building

Configure and build from the project root directory:

```bash
cmake --preset linux-debug
cmake --build --preset linux-debug
```

## Running Tests

Run all tests via the built `unittest` executable:

```bash
./build/linux-debug/test/unittest
```

Run specific test suites by passing suite names as arguments:

```bash
./build/linux-debug/test/unittest Array Hash Iterator
```
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog

## 4.11.0 (Unreleased)
## 4.11.0 (2026-02-17)

Enhancements:
This release focuses on improving memory management:
* C++ API is now GC safe
* C++ API wrappers no longer default to rb_cObject, avoiding unintended Object changes
* Enable Instance Registry for Ruby owned C++ objects to avoid double frees

Incompatible Changes:
* `InstanceRegistry.isEnabled` (boolean) has been replaced by an `InstanceRegistry.isEnabled` which is an enum (`Off`, `Owned`, `All`).
Expand All @@ -9,6 +15,7 @@ Incompatible Changes:
* C++ API wrappers now store their Ruby `VALUE` in a `Pin` instead of a raw `VALUE` field. Previously, wrappers were not GC-safe and Ruby could reclaim wrapped objects while C++ still referenced them. This is now fixed, and wrappers such as `Object` can be stored safely in containers like `std::vector`.
* The global `Object` constants (`Rice::Nil`, `Rice::True`, `Rice::False`, `Rice::Undef`) have been removed.
* `Object::test()` has been removed. Use `operator bool()` or `is_nil()` depending on the desired semantics.
* Remove `Builtin_Object` since it didn't serve a useful purpose

## 4.10.0 (2026-02-07)

Expand Down
2 changes: 1 addition & 1 deletion docs/cpp_api/struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ Struct inherits from [Class](class.md), which means it has access to:
* `define_singleton_method` - Add class methods
* `create` - Create instances

Struct::Instance inherits from `Builtin_Object<T_STRUCT>`, providing access to [Object](object.md) methods.
Struct::Instance inherits from [Object](object.md) and validates that wrapped values are Ruby structs (`T_STRUCT`).

---

Expand Down
2 changes: 1 addition & 1 deletion lib/rice/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Rice
VERSION = "4.10.0"
VERSION = "4.11.0"
end
11 changes: 3 additions & 8 deletions rice/Director.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ namespace Rice
public:
//! Construct new Director. Needs the Ruby object so that the
// proxy class can call methods on that object.
Director(Object self) : self_(self)
{
}
Director(Object self);

virtual ~Director() = default;

Expand All @@ -24,13 +22,10 @@ namespace Rice
* method, use this method to throw an exception in this case.
*/
[[noreturn]]
void raisePureVirtual() const
{
rb_raise(rb_eNotImpError, "Cannot call super() into a pure-virtual C++ method");
}
void raisePureVirtual() const;

//! Get the Ruby object linked to this C++ instance
Object getSelf() const { return self_; }
Object getSelf() const;

private:

Expand Down
20 changes: 20 additions & 0 deletions rice/Director.ipp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef Rice__Director__ipp_
#define Rice__Director__ipp_

namespace Rice
{
inline Director::Director(Object self) : self_(self)
{
}

inline void Director::raisePureVirtual() const
{
rb_raise(rb_eNotImpError, "Cannot call super() into a pure-virtual C++ method");
}

inline Object Director::getSelf() const
{
return self_;
}
}
#endif // Rice__Director__ipp_
4 changes: 2 additions & 2 deletions rice/cpp_api/Array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Rice
* \endcode
*/
class Array
: public Builtin_Object<T_ARRAY>
: public Object
{
public:
//! Construct a new array
Expand Down Expand Up @@ -243,4 +243,4 @@ namespace Rice
Array::Iterator<Array_Ptr_T, Value_T> const& it);
} // namespace Rice

#endif // Rice__Array__hpp_
#endif // Rice__Array__hpp_
16 changes: 9 additions & 7 deletions rice/cpp_api/Array.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@

namespace Rice
{
inline Array::Array() : Builtin_Object<T_ARRAY>(detail::protect(rb_ary_new))
inline Array::Array() : Object(detail::protect(rb_ary_new))
{
}

inline Array::Array(long capacity) : Builtin_Object<T_ARRAY>(detail::protect(rb_ary_new_capa, capacity))
inline Array::Array(long capacity) : Object(detail::protect(rb_ary_new_capa, capacity))
{
}

inline Array::Array(Object v) : Builtin_Object<T_ARRAY>(v)
inline Array::Array(Object v) : Object(v)
{
detail::protect(rb_check_type, this->value(), T_ARRAY);
}

inline Array::Array(VALUE v) : Builtin_Object<T_ARRAY>(v)
inline Array::Array(VALUE v) : Object(v)
{
detail::protect(rb_check_type, this->value(), T_ARRAY);
}

template<typename Iter_T>
inline Array::Array(Iter_T it, Iter_T end) : Builtin_Object<T_ARRAY>(detail::protect(rb_ary_new))
inline Array::Array(Iter_T it, Iter_T end) : Object(detail::protect(rb_ary_new))
{
for (; it != end; ++it)
{
Expand All @@ -29,7 +31,7 @@ namespace Rice
}

template<typename T, long n>
inline Array::Array(T (&a)[n]) : Builtin_Object<T_ARRAY>(detail::protect(rb_ary_new))
inline Array::Array(T (&a)[n]) : Object(detail::protect(rb_ary_new))
{
for (long j = 0; j < n; ++j)
{
Expand Down Expand Up @@ -442,4 +444,4 @@ namespace Rice::detail
Arg* arg_ = nullptr;
};
}
#endif // Rice__Array__ipp_
#endif // Rice__Array__ipp_
31 changes: 0 additions & 31 deletions rice/cpp_api/Builtin_Object.hpp

This file was deleted.

37 changes: 0 additions & 37 deletions rice/cpp_api/Builtin_Object.ipp

This file was deleted.

3 changes: 1 addition & 2 deletions rice/cpp_api/Hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Rice
//! h[10] = String("bar");
//! std::cout << String(h[42]) << std::endl;
//! \endcode
class Hash: public Builtin_Object<T_HASH>
class Hash: public Object
{
public:
//! Construct a new hash.
Expand Down Expand Up @@ -191,4 +191,3 @@ namespace Rice
} // namespace Rice

#endif // Rice__Hash__hpp_

5 changes: 3 additions & 2 deletions rice/cpp_api/Hash.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

namespace Rice
{
inline Hash::Hash() : Builtin_Object<T_HASH>(detail::protect(rb_hash_new))
inline Hash::Hash() : Object(detail::protect(rb_hash_new))
{
}

inline Hash::Hash(Object v) : Builtin_Object<T_HASH>(v)
inline Hash::Hash(Object v) : Object(v)
{
detail::protect(rb_check_type, this->value(), T_HASH);
}

inline size_t Hash::size() const
Expand Down
3 changes: 1 addition & 2 deletions rice/cpp_api/Module.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ namespace Rice
* Many of the methods are defined in Module_impl.hpp so that they can
* return a reference to the most derived type.
*/
// TODO: we can't inherit from Builtin_Object, because Class needs
// type T_CLASS and Module needs type T_MODULE
// Module and Class both derive from Object to preserve Ruby's hierarchy.
class Module : public Object
{
public:
Expand Down
4 changes: 2 additions & 2 deletions rice/cpp_api/String.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Rice
* std::cout << s.length() << std::endl;
* \endcode
*/
class String : public Builtin_Object<T_STRING>
class String : public Object
{
public:
//! Construct a new string.
Expand Down Expand Up @@ -70,4 +70,4 @@ namespace Rice
};
} // namespace Rice

#endif // Rice__String__hpp_
#endif // Rice__String__hpp_
18 changes: 10 additions & 8 deletions rice/cpp_api/String.ipp
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
namespace Rice
{
inline String::String() : Builtin_Object<T_STRING>(detail::protect(rb_str_new2, ""))
inline String::String() : Object(detail::protect(rb_str_new2, ""))
{
}

inline String::String(VALUE v) : Builtin_Object<T_STRING>(v)
inline String::String(VALUE v) : Object(v)
{
detail::protect(rb_check_type, this->value(), T_STRING);
}

inline String::String(Object v) : Builtin_Object<T_STRING>(v)
inline String::String(Object v) : Object(v)
{
detail::protect(rb_check_type, this->value(), T_STRING);
}

inline String::String(char const* s) : Builtin_Object<T_STRING>(detail::protect(rb_utf8_str_new_cstr, s))
inline String::String(char const* s) : Object(detail::protect(rb_utf8_str_new_cstr, s))
{
}

inline String::String(std::string const& s) : Builtin_Object<T_STRING>(detail::protect(rb_utf8_str_new, s.data(), (long)s.length()))
inline String::String(std::string const& s) : Object(detail::protect(rb_utf8_str_new, s.data(), (long)s.length()))
{
}

inline String::String(std::string_view const& s) : Builtin_Object<T_STRING>(detail::protect(rb_utf8_str_new, s.data(), (long)s.length()))
inline String::String(std::string_view const& s) : Object(detail::protect(rb_utf8_str_new, s.data(), (long)s.length()))
{
}

inline String::String(Identifier id) : Builtin_Object<T_STRING>(detail::protect(rb_utf8_str_new_cstr, id.c_str()))
inline String::String(Identifier id) : Object(detail::protect(rb_utf8_str_new_cstr, id.c_str()))
{
}

Expand Down Expand Up @@ -140,4 +142,4 @@ namespace Rice::detail
private:
Arg* arg_ = nullptr;
};
}
}
4 changes: 2 additions & 2 deletions rice/cpp_api/Struct.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ namespace Rice

//! An instance of a Struct
//! \sa Struct
class Struct::Instance : public Builtin_Object<T_STRUCT>
class Struct::Instance : public Object
{
public:
//! Create a new Instance of a Struct.
Expand Down Expand Up @@ -110,4 +110,4 @@ namespace Rice

} // namespace Rice

#endif // Rice__ruby_struct__hpp_
#endif // Rice__ruby_struct__hpp_
8 changes: 5 additions & 3 deletions rice/cpp_api/Struct.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ namespace Rice
}

inline Struct::Instance::Instance(Struct const& type, Array args) :
Builtin_Object<T_STRUCT>(type.new_instance(args)), type_(type)
Object(type.new_instance(args)), type_(type)
{
detail::protect(rb_check_type, this->value(), T_STRUCT);
}

inline Struct::Instance::Instance(Struct const& type, Object s) :
Builtin_Object<T_STRUCT>(s), type_(type)
Object(s), type_(type)
{
detail::protect(rb_check_type, this->value(), T_STRUCT);
}

inline Struct define_struct()
Expand Down Expand Up @@ -94,4 +96,4 @@ namespace Rice::detail
return rb_cStruct;
}
};
}
}
Loading