0

I'm writing a Ruby C extension, and need to call rb_raise() and pass it a char *. I also need to free that given char *. My current behavior won't free anything, and another approach I've tried lead me into undefined behavior. What is the correct way to do so ?

Here's a minimal example of my issue:

static VALUE
rb_some_function(VALUE self)
{
    char *foo;
    
    foo = malloc(100);
    strcpy(foo, "message\0");
    
    rb_raise(rb_eRuntimeError, "%s\n", foo);
    free(foo); // This won't work since rb_raise exits the function using longjmp()
}

My idea would be to free before rb_raise. I've tried this with a tiny program, to check if it would lead to any leak:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void stuff(int i)
{
    char *str;

    str = malloc(100);
    strcpy(str, "message\0");
    free(str); // this feel very wrong to free and then use
    printf("\r%s %i", str, i);
}

int main(int argc, char const *argv[])
{
    // Time for instrument to attach.
    sleep(5);
    for (int i = 0; i < 1e6; i++) stuff(i);
    printf("\n");
    return 0;
}

It seems that this is safe from leak. But freeing before usage makes me think this can lead to troubles I don't known about. Like an undefined behavior as suggested in this answer.

Do you know a safe way rb_raise and free the string that contained the error message ?

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
  • 'This won't work since rb_raise'......rb_raise() is broken, fix it. – Martin James Nov 14 '20 at 14:52
  • @Martin James, rb_raise is psrt of ruby core API. I hardly believe it is broken and cannot change it anyway. It uses longjmp internally. Anyway, fixing rb_raise is not an acceptable solution unfortunately. If you are the downvoter, please tell me how can I improve my question. I'll already link rb_raise reference. – Ulysse BN Nov 14 '20 at 15:01
  • OK, then the way you are calling/using rb_raise() is borken. Freeing the space, and then accessing it, is 100% wrong and is covering up the root cause of your problem with undefined behaviour. – Martin James Nov 14 '20 at 15:07
  • Ok thanks for removing the doubt on that one. However the question still stands, how can I use that function and free memory. Maybe this is a question for someone who knows the ruby C API, I'm clarifying the intro as well – Ulysse BN Nov 14 '20 at 15:10
  • @MartinJames edited, hope it is more clear now – Ulysse BN Nov 14 '20 at 15:20
  • 2
    Use a static array rather than allocating memory, perhaps? – user58697 Nov 14 '20 at 21:41
  • @user58697 well the data comes from an external library, the string may be very long. I feel like use a huge static array will have the drawback of polluting my stack with space i may never need. Right ? (I'm not the best c coder obviously) – Ulysse BN Nov 15 '20 at 12:13

1 Answers1

1

How about use Ruby String object like this.

static VALUE
rb_some_function(VALUE self)
{
    volatile VALUE str;
    char *foo;
    
    foo = malloc(100);
    strcpy(foo, "message\0");
    
    str = rb_str_new2(foo);
    free(foo); 

    rb_raise(rb_eRuntimeError, "%s\n", StringValuePtr(str));
}

If you use a Ruby String object, it will be freed by ruby's garbage collector.

binzo
  • 1,527
  • 1
  • 3
  • 13
  • Yes that looks like the best solution to leverage power of ruby's gc while avoiding to create an RData for a simple string! Thanks! Just a questio: why did you mark the str as volatile ? – Ulysse BN Nov 15 '20 at 12:11
  • 1
    I'm sorry to confuse you. It is a habit :-) Actually, this is an idiom to prevent the object refered by 'str' from being swept by GC. You may not need it in this small code. But I'm putting it in safety because I haven't seen the full form of your code. FIY, it is now recommended [to use a macro called RB_GC_GUARD() instead of 'volatile' modifier](https://docs.ruby-lang.org/en/2.4.0/extension_rdoc.html#label-Appendix+E.+RB_GC_GUARD+to+protect+from+premature+GC). – binzo Nov 16 '20 at 08:50