Skip to content

Commit df1f45c

Browse files
devs6186claude
andcommitted
fix(rb_loader): return TYPE_THROWABLE from invoke path on Ruby exception
When a Ruby function raises an exception, rb_protect() returns a non-zero state but the invoke path fell through to rb_type_deserialize() on Qnil, silently returning a TYPE_NULL value to the caller. The four TODO comments marking this were unfixed since the issue was opened. Replace the rb_loader_impl_print_last_exception macro (which only logged) with a static helper rb_loader_impl_exception_value() that extracts the pending exception from rb_errinfo(), builds a TYPE_THROWABLE value using exception_create_const/throwable_create/value_create_throwable — the same pattern used by py_loader_impl_error_value_from_exception() — and returns it immediately. The error is cleared with rb_set_errinfo(Qnil) so it does not propagate further up the Ruby stack. All four invoke branches (TYPED, DUCKTYPED, MIXED, zero-args) now return a structured throwable carrying the exception class name, message, and first backtrace line. Node.js callers receive a native JS Error via the existing TYPE_THROWABLE handling in node_loader_impl_value_to_napi(), and C callers can extract the exception with metacall_error_from_value(). Fixes #141 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 44c62f9 commit df1f45c

1 file changed

Lines changed: 30 additions & 28 deletions

File tree

source/loaders/rb_loader/source/rb_loader_impl.c

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -345,22 +345,32 @@ static VALUE rb_loader_impl_funcallv_kw_protect(VALUE args)
345345
return rb_funcallv_kw(protect->module_instance, protect->id, protect->argc, protect->argv, RB_PASS_KEYWORDS);
346346
}
347347

348-
/* TODO: Convert this into a return exception */
349-
#define rb_loader_impl_print_last_exception() \
350-
do \
351-
{ \
352-
VALUE e = rb_errinfo(); \
353-
if (e != Qnil) \
354-
{ \
355-
VALUE error; \
356-
VALUE bt = rb_funcall(e, rb_intern("backtrace"), 0); \
357-
VALUE msg = rb_funcall(e, rb_intern("message"), 0); \
358-
bt = rb_ary_entry(bt, 0); \
359-
error = rb_sprintf("%" PRIsVALUE ": %" PRIsVALUE " (%s)\n", bt, msg, rb_obj_classname(e)); \
360-
log_write("metacall", LOG_LEVEL_ERROR, "Exception raised in Ruby '%s'", RSTRING_PTR(error)); \
361-
rb_backtrace(); \
362-
} \
363-
} while (0)
348+
static value rb_loader_impl_exception_value(void)
349+
{
350+
static const char backtrace_not_found[] = "Backtrace not available";
351+
VALUE e = rb_errinfo();
352+
353+
if (e == Qnil)
354+
{
355+
return NULL;
356+
}
357+
358+
VALUE msg_obj = rb_funcall(e, rb_intern("message"), 0);
359+
VALUE bt_ary = rb_funcall(e, rb_intern("backtrace"), 0);
360+
VALUE bt_first = (bt_ary != Qnil && RARRAY_LEN(bt_ary) > 0) ? rb_ary_entry(bt_ary, 0) : Qnil;
361+
362+
const char *class_name = rb_obj_classname(e);
363+
const char *msg = (msg_obj != Qnil) ? StringValuePtr(msg_obj) : "";
364+
const char *bt = (bt_first != Qnil) ? StringValuePtr(bt_first) : backtrace_not_found;
365+
366+
log_write("metacall", LOG_LEVEL_ERROR, "Exception raised in Ruby '%s': %s", class_name, msg);
367+
368+
rb_set_errinfo(Qnil);
369+
370+
exception ex = exception_create_const(msg, class_name, 0, bt);
371+
throwable th = throwable_create(value_create_exception(ex));
372+
return value_create_throwable(th);
373+
}
364374

365375
function_return function_rb_interface_invoke(function func, function_impl impl, function_args args, size_t size)
366376
{
@@ -446,9 +456,7 @@ function_return function_rb_interface_invoke(function func, function_impl impl,
446456

447457
if (state != 0)
448458
{
449-
rb_loader_impl_print_last_exception();
450-
451-
// TODO: Throw exception?
459+
return rb_loader_impl_exception_value();
452460
}
453461
}
454462
else if (invoke_type == FUNCTION_RB_DUCKTYPED)
@@ -465,9 +473,7 @@ function_return function_rb_interface_invoke(function func, function_impl impl,
465473

466474
if (state != 0)
467475
{
468-
rb_loader_impl_print_last_exception();
469-
470-
// TODO: Throw exception ?
476+
return rb_loader_impl_exception_value();
471477
}
472478
}
473479
else if (invoke_type == FUNCTION_RB_MIXED)
@@ -486,9 +492,7 @@ function_return function_rb_interface_invoke(function func, function_impl impl,
486492

487493
if (state != 0)
488494
{
489-
rb_loader_impl_print_last_exception();
490-
491-
// TODO: Throw exception ?
495+
return rb_loader_impl_exception_value();
492496
}
493497
}
494498
}
@@ -506,9 +510,7 @@ function_return function_rb_interface_invoke(function func, function_impl impl,
506510

507511
if (state != 0)
508512
{
509-
rb_loader_impl_print_last_exception();
510-
511-
// TODO: Throw exception ?
513+
return rb_loader_impl_exception_value();
512514
}
513515
}
514516

0 commit comments

Comments
 (0)