Skip to content

Commit d8212aa

Browse files
committed
Fix InputCallbacks GC crash
1 parent cccf70f commit d8212aa

File tree

2 files changed

+81
-17
lines changed

2 files changed

+81
-17
lines changed

ext/libxml/ruby_xml_input_cbg.c

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,38 @@
88
* Support for adding custom scheme handlers. */
99

1010
static ic_scheme *first_scheme = 0;
11+
static int input_callbacks_registered = 0;
1112

12-
int ic_match(char const *filename)
13+
static int ic_match(char const *filename);
14+
static void* ic_open(char const *filename);
15+
static int ic_read(void *context, char *buffer, int len);
16+
static int ic_close(void *context);
17+
18+
static void input_callbacks_register_once(void)
19+
{
20+
if (input_callbacks_registered)
21+
return;
22+
23+
xmlRegisterInputCallbacks(ic_match, ic_open, ic_read, ic_close);
24+
input_callbacks_registered = 1;
25+
}
26+
27+
static char *ic_strdup(const char *string)
28+
{
29+
size_t length = strlen(string) + 1;
30+
char *copy = ruby_xmalloc(length);
31+
memcpy(copy, string, length);
32+
return copy;
33+
}
34+
35+
static void ic_scheme_free(ic_scheme *scheme)
36+
{
37+
rb_gc_unregister_address(&scheme->class);
38+
ruby_xfree(scheme->scheme_name);
39+
ruby_xfree(scheme);
40+
}
41+
42+
static int ic_match(char const *filename)
1343
{
1444
ic_scheme *scheme;
1545

@@ -27,7 +57,7 @@ int ic_match(char const *filename)
2757
return 0;
2858
}
2959

30-
void* ic_open(char const *filename)
60+
static void* ic_open(char const *filename)
3161
{
3262
ic_doc_context *ic_doc;
3363
ic_scheme *scheme;
@@ -38,12 +68,11 @@ void* ic_open(char const *filename)
3868
{
3969
if (!xmlStrncasecmp(BAD_CAST filename, BAD_CAST scheme->scheme_name, scheme->name_len))
4070
{
41-
ic_doc = (ic_doc_context*) malloc(sizeof(ic_doc_context));
42-
4371
res = rb_funcall(scheme->class, rb_intern("document_query"), 1,
4472
rb_str_new2(filename));
4573

46-
ic_doc->buffer = strdup(StringValuePtr(res));
74+
ic_doc = ALLOC(ic_doc_context);
75+
ic_doc->buffer = ic_strdup(StringValueCStr(res));
4776

4877
ic_doc->bpos = ic_doc->buffer;
4978
ic_doc->remaining = (int)strlen(ic_doc->buffer);
@@ -54,7 +83,7 @@ void* ic_open(char const *filename)
5483
return 0;
5584
}
5685

57-
int ic_read(void *context, char *buffer, int len)
86+
static int ic_read(void *context, char *buffer, int len)
5887
{
5988
ic_doc_context *ic_doc;
6089
int ret_len;
@@ -75,7 +104,7 @@ int ic_read(void *context, char *buffer, int len)
75104
return ret_len;
76105
}
77106

78-
int ic_close(void *context)
107+
static int ic_close(void *context)
79108
{
80109
ruby_xfree(((ic_doc_context*) context)->buffer);
81110
ruby_xfree(context);
@@ -90,7 +119,7 @@ int ic_close(void *context)
90119
*/
91120
static VALUE input_callbacks_register_input_callbacks(VALUE self)
92121
{
93-
xmlRegisterInputCallbacks(ic_match, ic_open, ic_read, ic_close);
122+
input_callbacks_register_once();
94123
return (Qtrue);
95124
}
96125

@@ -107,11 +136,14 @@ static VALUE input_callbacks_add_scheme(VALUE self, VALUE scheme_name,
107136

108137
Check_Type(scheme_name, T_STRING);
109138

110-
scheme = (ic_scheme*) malloc(sizeof(ic_scheme));
139+
input_callbacks_register_once();
140+
141+
scheme = ALLOC(ic_scheme);
111142
scheme->next_scheme = 0;
112-
scheme->scheme_name = strdup(StringValuePtr(scheme_name)); /* TODO alloc, dealloc */
143+
scheme->scheme_name = ic_strdup(StringValueCStr(scheme_name));
113144
scheme->name_len = (int)strlen(scheme->scheme_name);
114-
scheme->class = class; /* TODO alloc, dealloc */
145+
scheme->class = class;
146+
rb_gc_register_address(&scheme->class);
115147

116148
//fprintf( stderr, "registered: %s, %d, %s\n", scheme->scheme_name, scheme->name_len, scheme->class );
117149

@@ -150,9 +182,7 @@ static VALUE input_callbacks_remove_scheme(VALUE self, VALUE scheme_name)
150182
{
151183
save_scheme = first_scheme->next_scheme;
152184

153-
ruby_xfree(first_scheme->scheme_name);
154-
ruby_xfree(first_scheme);
155-
185+
ic_scheme_free(first_scheme);
156186
first_scheme = save_scheme;
157187
return Qtrue;
158188
}
@@ -165,9 +195,7 @@ static VALUE input_callbacks_remove_scheme(VALUE self, VALUE scheme_name)
165195
{
166196
save_scheme = scheme->next_scheme->next_scheme;
167197

168-
ruby_xfree(scheme->next_scheme->scheme_name);
169-
ruby_xfree(scheme->next_scheme);
170-
198+
ic_scheme_free(scheme->next_scheme);
171199
scheme->next_scheme = save_scheme;
172200
return Qtrue;
173201
}

test/test_input_callbacks.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# encoding: UTF-8
2+
3+
require_relative './test_helper'
4+
5+
class InputCallbacksGCStressRepro
6+
def run
7+
scheme_name = 'input-callback-gc://'
8+
9+
callback_class = Class.new do
10+
def self.document_query(_uri)
11+
'<root/>'
12+
end
13+
end
14+
LibXML::XML::InputCallbacks.add_scheme(scheme_name, callback_class)
15+
callback_class = nil
16+
17+
document = LibXML::XML::Document.file("#{scheme_name}fixture.xml")
18+
document.root.name
19+
ensure
20+
LibXML::XML::InputCallbacks.remove_scheme(scheme_name)
21+
end
22+
end
23+
24+
class TestInputCallbacks < Minitest::Test
25+
def setup
26+
GC.stress = true
27+
end
28+
29+
def teardown
30+
GC.stress = false
31+
end
32+
33+
def test_add_scheme_auto_registers_and_survives_gc_stress
34+
assert_equal('root', InputCallbacksGCStressRepro.new.run)
35+
end
36+
end

0 commit comments

Comments
 (0)