Skip to content

Commit 380287a

Browse files
author
Jim Flood
committed
Switch to rb_ary_new2 and rb_ary_push to prevent GC segfault on params
to user-defined function. The new test_function_gc_segfault is skipped because you don't want to see segfault when running rake test. Unskip the test and comment out rb_ary_push to see the test fail.
1 parent f1d103a commit 380287a

File tree

2 files changed

+16
-2
lines changed

2 files changed

+16
-2
lines changed

ext/sqlite3/database.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,17 +359,22 @@ static void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result)
359359
static void rb_sqlite3_func(sqlite3_context * ctx, int argc, sqlite3_value **argv)
360360
{
361361
VALUE callable = (VALUE)sqlite3_user_data(ctx);
362-
VALUE params[argc]; /* RB_GC_GUARD unreliable -- keep values safely on the stack */
362+
VALUE * params = NULL;
363+
VALUE guard = rb_ary_new2(argc); /* Prevent garbage collection of params during call. */
363364
VALUE result;
364365
int i;
365366

366367
if (argc > 0) {
368+
params = xcalloc((size_t)argc, sizeof(VALUE *));
367369
for(i = 0; i < argc; i++) {
368-
params[i] = sqlite3val2rb(argv[i]);
370+
VALUE param = sqlite3val2rb(argv[i]);
371+
rb_ary_push(guard, param);
372+
params[i] = param;
369373
}
370374
}
371375

372376
result = rb_funcall2(callable, rb_intern("call"), argc, params);
377+
xfree(params);
373378

374379
set_sqlite3_func_result(ctx, result);
375380
}

test/test_database.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,15 @@ def test_function_return_types
279279
end
280280
end
281281

282+
def test_function_gc_segfault
283+
skip("Segfault is unsettling to see when running rake test")
284+
@db.create_function("bug", -1) { |func, *values| func.result = values.join }
285+
# With a lot of data and a lot of threads, try to induce a GC segfault.
286+
params = Array.new(127, "?" * 28000)
287+
proc = Proc.new { db.execute("select bug(#{Array.new(params.length, "?").join(",")})", *params) }
288+
Mutex.new.tap { |m| threads = (0..30).inject([]) { |a| a << Thread.new { m.synchronize { proc.call } } }.each { |thread| thread.join } }
289+
end
290+
282291
def test_function_return_type_round_trip
283292
[10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing|
284293
@db.define_function("hello") { |a| a }

0 commit comments

Comments
 (0)