Skip to content

Commit

Permalink
Merge pull request #2 from lutaml/RTYPEDDATE_guard
Browse files Browse the repository at this point in the history
Throw an exception when wrapper is not available at addKeepAlive
  • Loading branch information
maxirmx authored Dec 27, 2023
2 parents 9545a1a + 3b8bc25 commit f038052
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 6 deletions.
2 changes: 2 additions & 0 deletions doc/bindings/memory_management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ Obviously this code could be rewritten to make sure the database object remains
define_class<Database>("Database")
.define_method("get_column", &Database::getColumn, Return().keepAlive())
Note that Return().keepAlive() will work with wrapped types only. An attempt to use it with native type will result in runtime exception.

C++ Referencing Ruby Objects
----------------------------

Expand Down
33 changes: 30 additions & 3 deletions include/rice/rice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1531,7 +1531,7 @@ namespace Rice::detail
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif
return static_cast<Wrapper*>(RTYPEDDATA_DATA(value));
return RTYPEDDATA_P(value) ? static_cast<Wrapper*>(RTYPEDDATA_DATA(value)) : nullptr;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
Expand Down Expand Up @@ -4052,6 +4052,9 @@ namespace Rice::detail
// Figure out what self is
Class_T getReceiver(VALUE self);

// Throw an exception when wrapper cannot be extracted
[[noreturn]] static void noWrapper(const VALUE klass, const std::string& wrapper);

// Do we need to keep alive any arguments?
void checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues);

Expand Down Expand Up @@ -4107,7 +4110,7 @@ namespace Rice::detail
NativeFunction<From_Ruby_T, Function_T, IsMethod>::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
: klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo)
{
// Create a tuple of NativeArgs that will convert the Ruby values to native values. For
// Create a tuple of NativeArgs that will convert the Ruby values to native values. For
// builtin types NativeArgs will keep a copy of the native value so that it
// can be passed by reference or pointer to the native function. For non-builtin types
// it will just pass the value through.
Expand Down Expand Up @@ -4284,23 +4287,47 @@ namespace Rice::detail
}
}

template<typename From_Ruby_T, typename Function_T, bool IsMethod>
void NativeFunction<From_Ruby_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
{
std::string message = std::string("Could not find wrapper for '") + rb_obj_classname(klass) + "' " + wrapper + " type.";
throw std::runtime_error(message);
}

template<typename From_Ruby_T, typename Function_T, bool IsMethod>
void NativeFunction<From_Ruby_T, Function_T, IsMethod>::checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues)
{
// Check function arguments
// selfWrapper will be nullptr if this(self) is a native type and not wrapped type
// it is highly unlikely that keepAlive is used in this case but we check anyway
Wrapper* selfWrapper = getWrapper(self);

// Check function arguments
for (const Arg& arg : (*this->methodInfo_))
{
if (arg.isKeepAlive())
{
if (selfWrapper == nullptr)
{
noWrapper(self, "self");
}
selfWrapper->addKeepAlive(rubyValues[arg.position]);
}
}

// Check return value
if (this->methodInfo_->returnInfo.isKeepAlive())
{
if (selfWrapper == nullptr)
{
noWrapper(self, "self");
}

// returnWrapper will be nullptr if returnValue is native type and not wrapped type
Wrapper* returnWrapper = getWrapper(returnValue);
if (returnWrapper == nullptr)
{
noWrapper(returnValue, "return");
}
returnWrapper->addKeepAlive(self);
}
}
Expand Down
3 changes: 3 additions & 0 deletions rice/detail/NativeFunction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ namespace Rice::detail
// Figure out what self is
Class_T getReceiver(VALUE self);

// Throw an exception when wrapper cannot be extracted
[[noreturn]] static void noWrapper(const VALUE klass, const std::string& wrapper);

// Do we need to keep alive any arguments?
void checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues);

Expand Down
28 changes: 26 additions & 2 deletions rice/detail/NativeFunction.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ namespace Rice::detail
NativeFunction<From_Ruby_T, Function_T, IsMethod>::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo)
: klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo)
{
// Create a tuple of NativeArgs that will convert the Ruby values to native values. For
// Create a tuple of NativeArgs that will convert the Ruby values to native values. For
// builtin types NativeArgs will keep a copy of the native value so that it
// can be passed by reference or pointer to the native function. For non-builtin types
// it will just pass the value through.
Expand Down Expand Up @@ -215,23 +215,47 @@ namespace Rice::detail
}
}

template<typename From_Ruby_T, typename Function_T, bool IsMethod>
void NativeFunction<From_Ruby_T, Function_T, IsMethod>::noWrapper(const VALUE klass, const std::string& wrapper)
{
std::string message = std::string("Could not find wrapper for '") + rb_obj_classname(klass) + "' " + wrapper + " type.";
throw std::runtime_error(message);
}

template<typename From_Ruby_T, typename Function_T, bool IsMethod>
void NativeFunction<From_Ruby_T, Function_T, IsMethod>::checkKeepAlive(VALUE self, VALUE returnValue, std::vector<VALUE>& rubyValues)
{
// Check function arguments
// selfWrapper will be nullptr if this(self) is a native type and not wrapped type
// it is highly unlikely that keepAlive is used in this case but we check anyway
Wrapper* selfWrapper = getWrapper(self);

// Check function arguments
for (const Arg& arg : (*this->methodInfo_))
{
if (arg.isKeepAlive())
{
if (selfWrapper == nullptr)
{
noWrapper(self, "self");
}
selfWrapper->addKeepAlive(rubyValues[arg.position]);
}
}

// Check return value
if (this->methodInfo_->returnInfo.isKeepAlive())
{
if (selfWrapper == nullptr)
{
noWrapper(self, "self");
}

// returnWrapper will be nullptr if returnValue is native type and not wrapped type
Wrapper* returnWrapper = getWrapper(returnValue);
if (returnWrapper == nullptr)
{
noWrapper(returnValue, "return");
}
returnWrapper->addKeepAlive(self);
}
}
Expand Down
2 changes: 1 addition & 1 deletion rice/detail/Wrapper.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ namespace Rice::detail
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif
return static_cast<Wrapper*>(RTYPEDDATA_DATA(value));
return RTYPEDDATA_P(value) ? static_cast<Wrapper*>(RTYPEDDATA_DATA(value)) : nullptr;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
Expand Down
79 changes: 79 additions & 0 deletions test/test_Keep_Alive_No_Wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "unittest.hpp"
#include "embed_ruby.hpp"
#include <rice/rice.hpp>
#include <rice/stl.hpp>

using namespace Rice;

TESTSUITE(Keep_Alive_No_Wrapper);

namespace
{
class Animal
{
public:
Animal(char const * name) : name_(name) {}
char const * getName() { return name_; }
virtual ~Animal() = default;
private:
char const * name_;
};

class Zoo
{
public:
Zoo(void)
{
pets_.push_back(new Animal("Bear"));
pets_.push_back(new Animal("Tiger"));
pets_.push_back(new Animal("Lion"));
}

~Zoo()
{
for(auto pet : pets_)
{
delete pet;
}
pets_.clear();
}

Object getPets(void) {
Array pets;
for(auto p: pets_) {
pets.push(p);
}
return pets;
}

private:
std::vector<Animal*> pets_;
};
}

SETUP(Keep_Alive_No_Wrapper)
{
embed_ruby();
}

TESTCASE(test_keep_alive_no_wrapper)
{
define_class<Animal>("Animal")
.define_constructor(Constructor<Animal, char const *>())
.define_method("get_name", &Animal::getName);

define_class<Zoo>("Zoo")
.define_constructor(Constructor<Zoo>())
.define_method("get_pets", &Zoo::getPets, Return().keepAlive());

Module m = define_module("TestingModule");
Object zoo = m.module_eval("@zoo = Zoo.new");

// get_pets returns an Array (native type) so Return().keepAlive()
// shall result in std::runtime_error
ASSERT_EXCEPTION_CHECK(
Exception,
m.module_eval("@zoo.get_pets.each do |pet| puts pet.name; end"),
ASSERT_EQUAL("Could not find wrapper for 'Array' return type.", ex.what())
);
}

0 comments on commit f038052

Please sign in to comment.