ruby GC 删除索引对象时 clang 崩溃的扩展
ruby extension for clang crash with segfault when GC delete Index object
我正在尝试为 clang 的 c 接口编写一些 ruby 扩展。我关注的是我包装在 Clangc::Index class 中的 CXIndex 结构。
我能够编译它,加载模块并创建一些 Clangc::Index class 就像在这个测试中一样:
#!/usr/bin/env ruby
require "minitest/autorun"
require "clangc"
class TestIndexCreation < MiniTest::Test
def test_new_index_dont_exclude_decl_from_pch_and_dont_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(false, false).class
end
def test_new_index_exclude_decl_from_pch_and_dont_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(true, false).class
end
def test_new_index_dont_exclude_decl_from_pch_and_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(false, true).class
end
def test_new_index_exclude_decl_from_pch_and_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(true, true).class
end
end
当我尝试用我的模块做这种事情时,问题就来了:
C 方式(效果很好)
#include <stdio.h>
#include "clang-c/Index.h"
/*
compile with:
clang -lclang -o index_manipulation index_manipulation.c
*/
int main(int argc, char *argv[]) {
CXIndex Index = clang_createIndex(1, 1);
clang_CXIndex_setGlobalOptions(Index, CXGlobalOpt_None);
printf("%u\n", CXGlobalOpt_None);
clang_disposeIndex(Index);
Index = clang_createIndex(1, 1);
clang_CXIndex_setGlobalOptions(Index, CXGlobalOpt_ThreadBackgroundPriorityForEditing);
printf("%u\n", CXGlobalOpt_ThreadBackgroundPriorityForEditing);
clang_disposeIndex(Index);
// CXGlobalOpt_ForIndexing); //CXGlobalOptFlags
return 0;
}
以及当 GC 尝试清理使用选项 Threadbackgroundpriorityforediting 创建的 Index 实例时崩溃的 ruby 方式:
# index global options test
#Clangc::GlobalOptFlags.constants
#=> [:None, :Threadbackgroundpriorityforindexing, :Threadbackgroundpriorityforediting, :Threadbackgroundpriorityforall]
class TestIndexGlobalOptions < MiniTest::Test
def setup
@cindex = Clangc::Index.new(true, true)
end
def test_index_set_global_options_None
flags = Clangc::GlobalOptFlags::None
@cindex.global_options = flags
assert_equal flags, @cindex.global_options
end
def test_index_set_global_options_editing
flags = Clangc::GlobalOptFlags::Threadbackgroundpriorityforediting
@cindex.global_options = flags
assert_equal flags, @cindex.global_options
end
end
这是测试的输出(请注意,我在 C 代码中包含了一些调试输出):
ruby test/Index_tests.rb
Run options: --seed 10378
# Running:
New class allocated at 0x139bfe0
New class ptr 0x139bfe0 with Index ptr 0x1057b30
.New class allocated at 0x1350a30
New class ptr 0x1350a30 with Index ptr 0x13769f0
.
Finished in 0.000871s, 2297.2451 runs/s, 2297.2451 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
SENTINEL index ptr 0x1057b30
SENTINEL class ptr 0x139bfe0
SENTINEL free end
SENTINEL index ptr 0x2013769f0
test/Index_tests.rb: [BUG] Segmentation fault at 0x0003e800002782
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0001 p:0000 s:0002 E:000f60 TOP [FINISH]
-- Machine register context ------------------------------------------------
RIP: 0x00007f175c9cfb9d RBP: 0x0000000000de99f0 RSP: 0x00007ffc5a05a000
RAX: 0x0000000000000000 RBX: 0x00000002013769f0 RCX: 0x000000007fffffe1
RDX: 0x00007f175fb29970 RDI: 0x00000002013769f0 RSI: 0x000000000000001f
R8: 0x00000002013769f0 R9: 0x0000000000000000 R10: 0x00007f176018e700
R11: 0x0000000000000000 R12: 0x0000000001040000 R13: 0x0000000001042648
R14: 0x000000000103e060 R15: 0x00007ffc5a05a048 EFL: 0x0000000000010206
-- C level backtrace information -------------------------------------------
/usr/lib/libruby.so.2.2 [0x7f175fcc2915]
/usr/lib/libruby.so.2.2 [0x7f175fcc2b4c]
/usr/lib/libruby.so.2.2 [0x7f175fb9cd4b]
/usr/lib/libruby.so.2.2 [0x7f175fc5432e]
/usr/lib/libc.so.6 [0x7f175f7bd540]
/usr/lib/libclang.so(clang_disposeIndex+0x1d) [0x7f175c9cfb9d]
/home/cedlemo/.gem/ruby/2.2.0/extensions/x86_64-linux/2.2.0/clangc-0.0.1/clangc/clangc.so(c_Index_struct_free+0x2c) [0x7f175d79ff7c]
/usr/lib/libruby.so.2.2 [0x7f175fbb3d21]
/usr/lib/libruby.so.2.2(rb_gc_call_finalizer_at_exit+0x289) [0x7f175fbbd169]
/usr/lib/libruby.so.2.2(ruby_cleanup+0x3e8) [0x7f175fba3668]
/usr/lib/libruby.so.2.2(ruby_run_node+0x25) [0x7f175fba38b5]
ruby [0x4008ab]
/usr/lib/libc.so.6(__libc_start_main+0xf0) [0x7f175f7aa800]
ruby(_start+0x29) [0x4008d9]
我知道这与 clang_disposeIndex 有关,但我找不到解决方法。
这是我的扩展文件:
├── ext
│ └── clangc
│ ├── clangc.c
│ ├── class_Index.c
│ ├── class_Index.h
│ ├── constants.c
│ ├── constants.h
│ ├── extconf.rb
class_Index.h
#ifndef INDEX_H
#define INDEX_H
#include <ruby/ruby.h>
typedef struct Index_t {
CXIndex data;
} Index_t;
VALUE generate_Index_under(VALUE, VALUE);
#endif //INDEX_H
class_Index.c
/*Index ruby class*/
#include "clang-c/Index.h"
#include "class_Index.h"
#include "stdio.h"
static void
c_Index_struct_free(Index_t *s)
{
if(s)
{
if(s->data)
{
printf("SENTINEL index ptr %p\n", s->data);
clang_disposeIndex(s->data);
}
printf("SENTINEL class ptr %p\n", s);
ruby_xfree(s);
printf("SENTINEL free end\n");
}
}
static VALUE
c_Index_struct_alloc( VALUE klass)
{
Index_t *i;
i = (Index_t *) ruby_xmalloc(sizeof(Index_t));
printf("New class allocated at %p\n", i);
i->data = NULL;
return Data_Wrap_Struct(klass, NULL, c_Index_struct_free,(void *) i );
}
static VALUE
c_Index_initialize(VALUE self, VALUE excl_decls_from_PCH, VALUE display_diagnostics) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
uint e= (excl_decls_from_PCH == Qtrue) ? 1 : 0;
uint d= (display_diagnostics == Qtrue) ? 1 : 0;
i->data = clang_createIndex( e, d);
printf("New class %p with Indew %p\n", i, i->data);
return self;
}
static VALUE
c_Index_set_global_options(VALUE self, VALUE options) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
if (TYPE(options) == T_FIXNUM || TYPE(options) == T_BIGNUM)
clang_CXIndex_setGlobalOptions(i,NUM2UINT(options));
else
rb_raise(rb_eTypeError, "invalid type for input");
return Qnil;
}
static VALUE
c_Index_get_global_options(VALUE self) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
return UINT2NUM(clang_CXIndex_getGlobalOptions(i));
}
VALUE
generate_Index_under(VALUE module, VALUE superclass){
VALUE klass = rb_define_class_under(module, "Index", superclass);
rb_define_alloc_func(klass, c_Index_struct_alloc);
rb_define_private_method(klass, "initialize", RUBY_METHOD_FUNC(c_Index_initialize), 2);
rb_define_method(klass, "global_options=", RUBY_METHOD_FUNC(c_Index_set_global_options), 1);
rb_define_method(klass, "global_options", RUBY_METHOD_FUNC(c_Index_get_global_options), 0);
return klass;
}
clangc.c
#include <ruby/ruby.h>
#include "clang-c/Index.h"
#include "constants.h"
#include "class_Index.h"
void Init_clangc(void) {
VALUE m_clangc = rb_define_module("Clangc");
init_clang_enums_to_constants(m_clangc);
generate_Index_under(m_clangc, rb_cObject);
}
我发现了错误,更正后的代码是:
static VALUE
c_Index_set_global_options(VALUE self, VALUE options) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
if (TYPE(options) == T_FIXNUM || TYPE(options) == T_BIGNUM)
//clang_CXIndex_setGlobalOptions(i,NUM2UINT(options));
clang_CXIndex_setGlobalOptions(i->data,NUM2UINT(options));
else
rb_raise(rb_eTypeError, "invalid type for input");
return Qnil;
}
static VALUE
c_Index_get_global_options(VALUE self) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
// return UINT2NUM(clang_CXIndex_getGlobalOptions(i));
return UINT2NUM(clang_CXIndex_getGlobalOptions(i->data));
}
我正在尝试为 clang 的 c 接口编写一些 ruby 扩展。我关注的是我包装在 Clangc::Index class 中的 CXIndex 结构。
我能够编译它,加载模块并创建一些 Clangc::Index class 就像在这个测试中一样:
#!/usr/bin/env ruby
require "minitest/autorun"
require "clangc"
class TestIndexCreation < MiniTest::Test
def test_new_index_dont_exclude_decl_from_pch_and_dont_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(false, false).class
end
def test_new_index_exclude_decl_from_pch_and_dont_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(true, false).class
end
def test_new_index_dont_exclude_decl_from_pch_and_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(false, true).class
end
def test_new_index_exclude_decl_from_pch_and_display_diagnostics
assert_equal Clangc::Index, Clangc::Index.new(true, true).class
end
end
当我尝试用我的模块做这种事情时,问题就来了: C 方式(效果很好)
#include <stdio.h>
#include "clang-c/Index.h"
/*
compile with:
clang -lclang -o index_manipulation index_manipulation.c
*/
int main(int argc, char *argv[]) {
CXIndex Index = clang_createIndex(1, 1);
clang_CXIndex_setGlobalOptions(Index, CXGlobalOpt_None);
printf("%u\n", CXGlobalOpt_None);
clang_disposeIndex(Index);
Index = clang_createIndex(1, 1);
clang_CXIndex_setGlobalOptions(Index, CXGlobalOpt_ThreadBackgroundPriorityForEditing);
printf("%u\n", CXGlobalOpt_ThreadBackgroundPriorityForEditing);
clang_disposeIndex(Index);
// CXGlobalOpt_ForIndexing); //CXGlobalOptFlags
return 0;
}
以及当 GC 尝试清理使用选项 Threadbackgroundpriorityforediting 创建的 Index 实例时崩溃的 ruby 方式:
# index global options test
#Clangc::GlobalOptFlags.constants
#=> [:None, :Threadbackgroundpriorityforindexing, :Threadbackgroundpriorityforediting, :Threadbackgroundpriorityforall]
class TestIndexGlobalOptions < MiniTest::Test
def setup
@cindex = Clangc::Index.new(true, true)
end
def test_index_set_global_options_None
flags = Clangc::GlobalOptFlags::None
@cindex.global_options = flags
assert_equal flags, @cindex.global_options
end
def test_index_set_global_options_editing
flags = Clangc::GlobalOptFlags::Threadbackgroundpriorityforediting
@cindex.global_options = flags
assert_equal flags, @cindex.global_options
end
end
这是测试的输出(请注意,我在 C 代码中包含了一些调试输出):
ruby test/Index_tests.rb
Run options: --seed 10378
# Running:
New class allocated at 0x139bfe0
New class ptr 0x139bfe0 with Index ptr 0x1057b30
.New class allocated at 0x1350a30
New class ptr 0x1350a30 with Index ptr 0x13769f0
.
Finished in 0.000871s, 2297.2451 runs/s, 2297.2451 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
SENTINEL index ptr 0x1057b30
SENTINEL class ptr 0x139bfe0
SENTINEL free end
SENTINEL index ptr 0x2013769f0
test/Index_tests.rb: [BUG] Segmentation fault at 0x0003e800002782
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0001 p:0000 s:0002 E:000f60 TOP [FINISH]
-- Machine register context ------------------------------------------------
RIP: 0x00007f175c9cfb9d RBP: 0x0000000000de99f0 RSP: 0x00007ffc5a05a000
RAX: 0x0000000000000000 RBX: 0x00000002013769f0 RCX: 0x000000007fffffe1
RDX: 0x00007f175fb29970 RDI: 0x00000002013769f0 RSI: 0x000000000000001f
R8: 0x00000002013769f0 R9: 0x0000000000000000 R10: 0x00007f176018e700
R11: 0x0000000000000000 R12: 0x0000000001040000 R13: 0x0000000001042648
R14: 0x000000000103e060 R15: 0x00007ffc5a05a048 EFL: 0x0000000000010206
-- C level backtrace information -------------------------------------------
/usr/lib/libruby.so.2.2 [0x7f175fcc2915]
/usr/lib/libruby.so.2.2 [0x7f175fcc2b4c]
/usr/lib/libruby.so.2.2 [0x7f175fb9cd4b]
/usr/lib/libruby.so.2.2 [0x7f175fc5432e]
/usr/lib/libc.so.6 [0x7f175f7bd540]
/usr/lib/libclang.so(clang_disposeIndex+0x1d) [0x7f175c9cfb9d]
/home/cedlemo/.gem/ruby/2.2.0/extensions/x86_64-linux/2.2.0/clangc-0.0.1/clangc/clangc.so(c_Index_struct_free+0x2c) [0x7f175d79ff7c]
/usr/lib/libruby.so.2.2 [0x7f175fbb3d21]
/usr/lib/libruby.so.2.2(rb_gc_call_finalizer_at_exit+0x289) [0x7f175fbbd169]
/usr/lib/libruby.so.2.2(ruby_cleanup+0x3e8) [0x7f175fba3668]
/usr/lib/libruby.so.2.2(ruby_run_node+0x25) [0x7f175fba38b5]
ruby [0x4008ab]
/usr/lib/libc.so.6(__libc_start_main+0xf0) [0x7f175f7aa800]
ruby(_start+0x29) [0x4008d9]
我知道这与 clang_disposeIndex 有关,但我找不到解决方法。
这是我的扩展文件:
├── ext
│ └── clangc
│ ├── clangc.c
│ ├── class_Index.c
│ ├── class_Index.h
│ ├── constants.c
│ ├── constants.h
│ ├── extconf.rb
class_Index.h
#ifndef INDEX_H
#define INDEX_H
#include <ruby/ruby.h>
typedef struct Index_t {
CXIndex data;
} Index_t;
VALUE generate_Index_under(VALUE, VALUE);
#endif //INDEX_H
class_Index.c
/*Index ruby class*/
#include "clang-c/Index.h"
#include "class_Index.h"
#include "stdio.h"
static void
c_Index_struct_free(Index_t *s)
{
if(s)
{
if(s->data)
{
printf("SENTINEL index ptr %p\n", s->data);
clang_disposeIndex(s->data);
}
printf("SENTINEL class ptr %p\n", s);
ruby_xfree(s);
printf("SENTINEL free end\n");
}
}
static VALUE
c_Index_struct_alloc( VALUE klass)
{
Index_t *i;
i = (Index_t *) ruby_xmalloc(sizeof(Index_t));
printf("New class allocated at %p\n", i);
i->data = NULL;
return Data_Wrap_Struct(klass, NULL, c_Index_struct_free,(void *) i );
}
static VALUE
c_Index_initialize(VALUE self, VALUE excl_decls_from_PCH, VALUE display_diagnostics) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
uint e= (excl_decls_from_PCH == Qtrue) ? 1 : 0;
uint d= (display_diagnostics == Qtrue) ? 1 : 0;
i->data = clang_createIndex( e, d);
printf("New class %p with Indew %p\n", i, i->data);
return self;
}
static VALUE
c_Index_set_global_options(VALUE self, VALUE options) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
if (TYPE(options) == T_FIXNUM || TYPE(options) == T_BIGNUM)
clang_CXIndex_setGlobalOptions(i,NUM2UINT(options));
else
rb_raise(rb_eTypeError, "invalid type for input");
return Qnil;
}
static VALUE
c_Index_get_global_options(VALUE self) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
return UINT2NUM(clang_CXIndex_getGlobalOptions(i));
}
VALUE
generate_Index_under(VALUE module, VALUE superclass){
VALUE klass = rb_define_class_under(module, "Index", superclass);
rb_define_alloc_func(klass, c_Index_struct_alloc);
rb_define_private_method(klass, "initialize", RUBY_METHOD_FUNC(c_Index_initialize), 2);
rb_define_method(klass, "global_options=", RUBY_METHOD_FUNC(c_Index_set_global_options), 1);
rb_define_method(klass, "global_options", RUBY_METHOD_FUNC(c_Index_get_global_options), 0);
return klass;
}
clangc.c
#include <ruby/ruby.h>
#include "clang-c/Index.h"
#include "constants.h"
#include "class_Index.h"
void Init_clangc(void) {
VALUE m_clangc = rb_define_module("Clangc");
init_clang_enums_to_constants(m_clangc);
generate_Index_under(m_clangc, rb_cObject);
}
我发现了错误,更正后的代码是:
static VALUE
c_Index_set_global_options(VALUE self, VALUE options) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
if (TYPE(options) == T_FIXNUM || TYPE(options) == T_BIGNUM)
//clang_CXIndex_setGlobalOptions(i,NUM2UINT(options));
clang_CXIndex_setGlobalOptions(i->data,NUM2UINT(options));
else
rb_raise(rb_eTypeError, "invalid type for input");
return Qnil;
}
static VALUE
c_Index_get_global_options(VALUE self) {
Index_t *i;
Data_Get_Struct(self, Index_t, i);
// return UINT2NUM(clang_CXIndex_getGlobalOptions(i));
return UINT2NUM(clang_CXIndex_getGlobalOptions(i->data));
}