Mruby: Errors in Ruby code and how to find them
by Paweł Świątkowski
06 Oct 2024
In the article about using mrbgems we had a situation when ARGV
constant was not defined, but referenced. As a result the code execution failed (the side-effects were not produced), however it did that completely silently. No error message was emitted. And even the exit code was zero.
This is obviously bad. So I set to fix it. By examining mruby source code I discovered mrb_print_error
function. Putting it to use looks like this:
#define MRUBY_ERROR 1
// ...
/* handle exceptions */
if(mrb->exc) {
fputs("Error when executing Ruby code:\n", stderr);
mrb_print_error(mrb);
mrb_close(mrb);
return MRUBY_ERROR;
}
We can invoke it by referencing some undefined constant in our Ruby code, for example:
p THIS_IS_UNDEFINED
Then after compilation and running we get:
Error when executing Ruby code:
(unknown):0: uninitialized constant THIS_IS_UNDEFINED (NameError)
We can also verify that the exit code was set.
$ echo $?
1
Of course, a (not so) small problem here is that the reference to original line is lost, but it’s still better than the silence. The good news, however, is that we can fix this!
When compiling Ruby code into the bytecode using mrbc
program we can specify the -g
flag. From the documentation it’s to “produce debugging information”. Now if we run our final executable we will get more information:
Error when executing Ruby code:
trace (most recent call last):
main.rb:42: uninitialized constant THIS_IS_UNDEFINED (NameError)
It might be interesting to see what’s the size difference in bytecode for versio with debugging information and without. For my simple script it looks as follows:
- Ruby script: 889 bytes
- C file with bytecode without debugging information: 6364
- Analogous C file with debugging information: 7503
As you can see, it’s certainly not for free. The size is almost 18% larger. However at least for development phase the gain in having precise information about the source of error is huge.