如果用户在使用Rice重新定义Ruby中的initialize(),则避免使用C ++代码中的Segfault

时间:2012-09-19 14:25:17

标签: c++ ruby

我在为Ruby编写C ++扩展时遇到的一个问题是,即使用户做了愚蠢的事情,也要确保它真正安全。他应该获得例外,但绝不会是SegFault。具体问题如下:我的C ++类有一个非平凡的构造函数。然后我使用Rice API来包装我的C ++类。如果用户在他的Ruby代码中重新定义了initialize(),则会覆盖Rice创建的initialize()函数,并且既不分配也不初始化该对象。一个玩具示例如下:

class Person {
public:
  Person(const string& name): m_name (name) {}
  const string& name() const { return m_name; }
private:
  string m_name;
}

然后我像这样创建Ruby类:

define_class<Person>("Person")
  .define_constructor(Constructor<Person, const string&>(), Arg("name"))
  .define_method("name", &Person::name);

然后,以下Ruby代码会导致Segfault

require 'MyExtension'
class Person
  def initialize
  end
end
p = Person.new
puts p.name

我会很高兴有两种可能:禁止以某种方式覆盖Ruby中的初始化函数或检入C ++,如果Object已正确分配,如果没有,则抛出异常。

我曾经直接使用Ruby C API然后很容易。我刚刚在allocate()函数和initialize方法中分配了一个由Null指针和一个设置为false的标志组成的虚拟对象,我分配了真实对象并将标志设置为true。在每个方法中,我检查了该标志并引发异常,如果它是假的。但是,我用Ruby C API写了很多愚蠢的重复代码,我首先必须包装我的C ++类,以便它们可以从C访问,然后包装和解包Ruby类型等,另外我必须检查这个愚蠢的标志每一种方法,所以我迁移到赖斯,这真的很好,我很高兴。

但是,在Rice中,程序员只能提供一个在rice创建的initialize()函数中调用的构造函数,而allocate()函数是预定义的,什么都不做。我不认为有一种简单的方法可以改变这个或以“官方”方式提供自己的分配功能。当然,我仍然可以使用C API来定义分配函数,所以我试图以某种方式混合C API和Rice,但后来我真的很讨厌,我得到了奇怪的SegFaults并且它真的很难看,所以我放弃了这个想法

这里有没有人有赖斯的经验,或者有人知道如何安全吗?

4 个答案:

答案 0 :(得分:4)

这个怎么样

class Person
  def initialize
    puts "old"
  end
  alias_method :original_initialize, :initialize

  def self.method_added(n)
    if n == :initialize && !@adding_initialize_method
      method_name = "new_initialize_#{Time.now.to_i}"
      alias_method method_name, :initialize
      begin
        @adding_initialize_method = true
        define_method :initialize do |*args|
          original_initialize(*args)
          send method_name, *args
        end
      ensure
        @adding_initialize_method = false
      end
    end
  end
end

class Person
  def initialize
    puts "new"
  end
end

然后调用Person.new输出

old
new

即。我们的旧初始化方法仍然被称为

这使用method_added挂钩,只要添加(或重新定义)方法,此时新方法已经存在,就会调用它,因此要阻止它们执行此操作为时已晚。相反,我们使用新定义的initialize方法(您可能希望更加努力地确保方法名称是唯一的),并定义另一个初始化,首先调用旧的初始化方法,然后再调用新的初始化方法。

如果这个人是明智的并且从他们的初始化调用super那么这将导致你的原始初始化方法被调用两次 - 你可能需要防范这个

你可以从method_added抛出异常来警告用户他们做了一件坏事,但这并没有阻止添加该方法:该类现在处于不稳定状态。当然,你可以在它们之上实现原始的初始化方法。

答案 1 :(得分:2)

在你的评论中,你说在c ++代码中,this是一个空指针。如果可以从ruby那里调用c ++类,我担心没有真正的解决方案。 C ++并非设计为万无一失。基本上这发生在c ++;

Person * p = 0;
p->name();

一个好的c ++编译器会阻止你这样做,但你总是可以用编译器无法检测到发生的事情的方式重写它。这会导致未定义的行为,程序可以执行任何操作,包括崩溃。

当然,您可以在每个非静态函数中检查这一点;

const string& Person::name() const 
{ 
    if (!this) throw "object not allocated";
    return m_name; 
}

为了简化并避免双重代码,请创建#define;

#define CHECK if (!this) { throw "object not allocated"; }

const string& name() const { CHECK; return m_name; }
int age() const { CHECK; return m_age; }

然而,最好避免在ruby中用户可以重新定义初始化。

答案 2 :(得分:1)

这是一个有趣的问题,不是Rice的特有问题,而是任何将分配和初始化分离为单独方法的扩展。我没有看到明显的解决方案。

在Ruby 1.6天,我们没有分配/初始化;我们有新的/初始化。 Rice中可能仍然存在代码,它定义了MyClass.new而不是MyClass.allocate和MyClass #initialize。 Ruby 1.8将分配和初始化分离为单独的方法(http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/23358),Rice使用新的“分配框架”。但它有你指出的问题。

allocate方法不能构造对象,因为它没有传递给构造函数的参数。

Rice可以定义.new(就像在1.6上所做的那样),但这不适用于#dup和Marshal.load。但是,这可能是更安全(和正确)的解决方案。

答案 3 :(得分:0)

我现在认为这是Rice图书馆的一个问题。如果你以记录的方式使用Rice,你会遇到这些问题并且没有明显的方法可以解决它,并且所有的变通方法都有缺点并且非常糟糕。所以我想解决方案是分叉Rice并修复它,因为它们似乎忽略了错误报告。