Skip to content

Commit bfd19f8

Browse files
yancyaclaude
authored andcommitted
fix: mark Logger in Parser GC to prevent use-after-free
Parser was not marking the Logger Ruby object during GC, causing the Logger to be collected while still referenced by tree-sitter's C callback. This adds mark/compact functions to parser_t and stores the Logger VALUE so GC keeps it alive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2cc3388 commit bfd19f8

File tree

2 files changed

+24
-2
lines changed

2 files changed

+24
-2
lines changed

ext/tree_sitter/parser.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ VALUE cParser;
77
typedef struct {
88
TSParser *data;
99
size_t cancellation_flag;
10+
VALUE logger;
1011
} parser_t;
1112

1213
static void parser_free(void *ptr) {
@@ -19,14 +20,24 @@ static size_t parser_memsize(const void *ptr) {
1920
return sizeof(type);
2021
}
2122

23+
static void parser_mark(void *ptr) {
24+
parser_t *parser = (parser_t *)ptr;
25+
rb_gc_mark_movable(parser->logger);
26+
}
27+
28+
static void parser_compact(void *ptr) {
29+
parser_t *parser = (parser_t *)ptr;
30+
parser->logger = rb_gc_location(parser->logger);
31+
}
32+
2233
const rb_data_type_t parser_data_type = {
2334
.wrap_struct_name = "parser",
2435
.function =
2536
{
26-
.dmark = NULL,
37+
.dmark = parser_mark,
2738
.dfree = parser_free,
2839
.dsize = parser_memsize,
29-
.dcompact = NULL,
40+
.dcompact = parser_compact,
3041
},
3142
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
3243
};
@@ -37,6 +48,7 @@ static VALUE parser_allocate(VALUE klass) {
3748
parser_t *parser;
3849
VALUE res = TypedData_Make_Struct(klass, parser_t, &parser_data_type, parser);
3950
parser->data = ts_parser_new();
51+
parser->logger = Qnil;
4052
return res;
4153
}
4254

@@ -178,6 +190,7 @@ static VALUE parser_get_logger(VALUE self) {
178190
*/
179191
static VALUE parser_set_logger(VALUE self, VALUE logger) {
180192
ts_parser_set_logger(SELF, value_to_logger(logger));
193+
unwrap(self)->logger = logger;
181194
return Qnil;
182195
}
183196

test/tree_sitter/logger_test.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ def capture_stderr
5757
refute_equal 0, backend.length
5858
end
5959

60+
it 'should not crash when GC runs after logger is set' do
61+
backend = StringIO.new
62+
parser.logger = TreeSitter::Logger.new(backend)
63+
GC.start
64+
GC.compact if GC.respond_to?(:compact)
65+
parser.parse_string(nil, program)
66+
refute_empty backend.string
67+
end
68+
6069
it 'should format output when a format string is passed' do
6170
delim = '~~~~~'
6271
backend = StringIO.new

0 commit comments

Comments
 (0)