Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way to wrap C language structure? #9

Closed
mattn opened this issue Apr 20, 2012 · 24 comments
Closed

Way to wrap C language structure? #9

mattn opened this issue Apr 20, 2012 · 24 comments

Comments

@mattn
Copy link
Contributor

mattn commented Apr 20, 2012

Where is function to embed wrapped object that like ruby's Data_Make_Struct. Or way to add trick to wrap it?

Thanks.

@matz
Copy link
Member

matz commented Apr 20, 2012

The following is the code from (undisclosed) socket extension:

#include "mruby/data.h"

static void
sock_free(mrb_state *mrb, void *p)
{
  close((int)p);
}

static const struct mrb_data_type sock_data_type = {
  "socket", sock_free,
};

VALUE
rsock_new_sock(mrb_state *mrb, struct RClass *c, int fd)
{
  return mrb_obj_value(Data_Wrap_Struct(mrb, c, &sock_data_type, (void*)fd));
}

Hope this helps to grab the code structure.

@matz matz closed this as completed Apr 20, 2012
@mattn
Copy link
Contributor Author

mattn commented Apr 20, 2012

Thank you.

@mattn
Copy link
Contributor Author

mattn commented Apr 20, 2012

Are you going to add alloc_func?

@matz
Copy link
Member

matz commented Apr 24, 2012

use MRB_SET_INSTANCE_TT(c, t) to specify instance object type. See src/array.c for example.

@mattn
Copy link
Contributor Author

mattn commented Apr 24, 2012

I'll look into it. thanks.

@halfdan
Copy link

halfdan commented Nov 1, 2012

Is the Socket extension available anywhere? I don't want to reinvent the wheel..

@masuidrive
Copy link
Contributor

https://github.com/iij/mruby provided extended version mruby. it's included TCPSocket and UNIXSocket.
iij said they will make pull request and send to master.

@halfdan
Copy link

halfdan commented Nov 1, 2012

@masuidrive, from what I understand, this will not be included in mruby as socket handling is not platform independent. Thanks for the link!

zh pushed a commit to zh/mruby that referenced this issue Dec 12, 2012
kou referenced this issue in kou/mruby Aug 17, 2015
The following code crashes without this change:

    def a
      [1].each do
        [2].each do
          [3].each do
            raise "XXX"
          end
        end
      end
    end

    begin
      a
    rescue => exception
      GC.start
      exception.backtrace
    end

GDB backtrace:

    Program received signal SIGSEGV, Segmentation fault.
    strlen () at ../sysdeps/x86_64/strlen.S:106
    106	../sysdeps/x86_64/strlen.S: No such file or directory.
    (gdb) bt
    #0  strlen () at ../sysdeps/x86_64/strlen.S:106
    #1  0x00000000004252cd in mrb_str_new_cstr (mrb=0x69f010,
        p=0x101 <error: Cannot access memory at address 0x101>)
        at mruby/src/string.c:290
    #2  0x00000000004183fe in get_backtrace_i (mrb=0x69f010, loc=0x7fffffffd940,
        data=0x6a7410) at mruby/src/backtrace.c:72
    #3  0x0000000000418793 in output_backtrace (mrb=0x69f010, ciidx=8,
        pc0=0x71940c, func=0x4183af <get_backtrace_i>, data=0x6a7410)
        at mruby/src/backtrace.c:140
    #4  0x0000000000418862 in exc_output_backtrace (mrb=0x69f010, exc=0x6a5be0,
        func=0x4183af <get_backtrace_i>, stream=0x6a7410)
        at mruby/src/backtrace.c:157
    #5  0x000000000041894c in mrb_exc_backtrace (mrb=0x69f010, self=...)
        at mruby/src/backtrace.c:199
    #6  0x000000000040dbaf in mrb_context_run (mrb=0x69f010, proc=0x6a61b0,
        self=..., stack_keep=0) at mruby/src/vm.c:1126
    #7  0x00000000004131d8 in mrb_toplevel_run_keep (mrb=0x69f010, proc=0x6a61b0,
        stack_keep=0) at mruby/src/vm.c:2422
    #8  0x000000000043a46c in load_exec (mrb=0x69f010, p=0x6f6450, c=0x6c9320)
        at mruby/mrbgems/mruby-compiler/core/parse.y:5619
    #9  0x000000000043a4e2 in mrb_load_file_cxt (mrb=0x69f010, f=0x6f61f0,
        c=0x6c9320)
        at mruby/mrbgems/mruby-compiler/core/parse.y:5628
    #10 0x0000000000402466 in main (argc=2, argv=0x7fffffffe438)
        at mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:222
@thomthom
Copy link

thomthom commented Oct 9, 2015

In the example:

VALUE
rsock_new_sock(mrb_state *mrb, struct RClass *c, int fd)
{
  return mrb_obj_value(Data_Wrap_Struct(mrb, c, &sock_data_type, (void*)fd));
}

So that is mapped to the new method - not initialize?

And what does MRB_SET_INSTANCE_TT(c, t) actually do?

I've written Ruby C Extensions before, for normal ruby, but I'm confused to how to do this for mruby. For normal ruby you have separate functions for allocation and initialization.

For instance, this example from http://silverhammermba.github.io/emberb/c/#data - how does one translate that into mruby?

(I've been looking at the source for mruby and the gems it ship, but I struggle to understand what is going on.)

#include <stdlib.h>

void foo_free(int* data)
{
    free(data);
}

VALUE foo_alloc(VALUE self)
{
    /* allocate */
    int* data = malloc(sizeof(int));

    /* wrap */
    return Data_Wrap_Struct(self, NULL, foo_free, data);
}

VALUE foo_m_initialize(VALUE self, VALUE val)
{
    int* data;
    /* unwrap */
    Data_Get_Struct(self, int, data);

    *data = NUM2INT(val);

    return self;
}

void some_func()
{
    /* ... */

    VALUE cFoo = rb_define_class("Foo", rb_cObject);

    rb_define_alloc_func(cFoo, foo_alloc);
    rb_define_method(cFoo, "initialize", foo_m_initialize, 1);

    /* ... */
}

@thomthom
Copy link

thomthom commented Oct 9, 2015

Looking at random.c:

static mrb_value
mrb_random_init(mrb_state *mrb, mrb_value self)
{
  mrb_value seed;
  mt_state *t;

  /* avoid memory leaks */
  t = (mt_state*)DATA_PTR(self);
  if (t) {
    mrb_free(mrb, t);
  }
  mrb_data_init(self, NULL, &mt_state_type);

  t = (mt_state *)mrb_malloc(mrb, sizeof(mt_state));
  t->mti = N + 1;

  seed = get_opt(mrb);
  seed = mrb_random_mt_srand(mrb, t, seed);
  if (mrb_nil_p(seed)) {
    t->has_seed = FALSE;
  }
  else {
    mrb_assert(mrb_fixnum_p(seed));
    t->has_seed = TRUE;
    t->seed = mrb_fixnum(seed);
  }

  mrb_data_init(self, t, &mt_state_type);

  return self;
}

This one uses mrb_data_init - is there any description of what this does?
There used to be C API docs on the mruby website - but that changed yesterday as I was going trough it. Where they moved?

@cremno
Copy link
Contributor

cremno commented Oct 10, 2015

So that is mapped to the new method - not initialize?

Neither. It's just an internal function. In mruby all C functions implementing Ruby methods must have the type mrb_value (*)(mrb_state*, mrb_value). The first argument is the VM state and the second one is Ruby's self.

And what does MRB_SET_INSTANCE_TT(c, t) actually do?

To tell mruby that instances of class c (struct RClass*) should have the type t (enum mrb_vtype). So it's a(nother) type system to differentiate mrb_values. Similar to CRuby's T_* macros.

I've written Ruby C Extensions before, for normal ruby, but I'm confused to how to do this for mruby. For normal ruby you have separate functions for allocation and initialization.

You have to do the allocations in #initialize.

This one uses mrb_data_init - is there any description of what this does?

It isn't documented yet. But it's just a shortcut for setting both the data pointer and type. See include/mruby/data.h. Those two macros do the same as in CRuby.

@cremno
Copy link
Contributor

cremno commented Oct 10, 2015

how does one translate that into mruby?

https://gist.github.com/cremno/a75a41bee003077d9a01 (untested and it's an example anyway)

@thomthom
Copy link

Thank you so much for your response and code snippet.
I tried it - only made a couple of adjustments - but it works: https://gist.github.com/thomthom/1064f8015c9c0491c39f

Btw, checking DATA_GET_PTR and freeing the memory before allocating in initialize - is that something particular to random.c - or something all mruby classes should do? It's unclear to me why the data pointer would have allocated memory at that point.

@cremno
Copy link
Contributor

cremno commented Oct 10, 2015

In Ruby initialize may be called several times. That isn't really mruby-specific but compared to your original foo example it might look like that. However imagine having to allocate memory in initialize because you want to allocate a variable amount of memory.

A helper function might be useful though :

mrb_int *data = (mrb_int*)DATA_PTR(self);
if (data) foo_free(mrb, data);
mrb_data_init(self, NULL, &foo_data_type);

Would become:

mrb_int *data; // for later?
mrb_data_foobar(self, &foo_data_type);

@furunkel
Copy link
Contributor

It's unfortunate that the DATA_PTR macro (and a lot of other macros) aren't prefixed properly, possibly causing name clashes with the host application.
@matz, maybe it would be a good idea to rename these now (and possibly add a compatibility macro for the next few releases), as mruby is still in a relatively early phase. There is mrb_data_get_ptr BTW, which does the same thing as the macro with an additional
type check.

@matz
Copy link
Member

matz commented Oct 10, 2015

It's inherited from CRuby, but maybe we should rename it to RDATA_PTR() to avoid potential name clash and for consistency.

@furunkel
Copy link
Contributor

Well, let's not inherit cruft from CRuby then ? ;)
RDATA_PTR() would be an improvement, though.

@thomthom
Copy link

When should one use MRB_SET_INSTANCE_TT(klass, MRB_TT_DATA); ? Is it needed to use DATA_PTR?

@matz
Copy link
Member

matz commented Oct 11, 2015

Whenever you create RData object. To use RDATA_PTR, you have to get RData object first.

@thomthom
Copy link

Thanks for confirming.
One more question: DATA_GET_PTR vs DATA_PTR? What is their different usage?

@cremno
Copy link
Contributor

cremno commented Oct 12, 2015

DATA_PTR doesn't check if the type of its mrb_value argument is MRB_TT_DATA and if the DATA_TYPE is a specific mrb_data_type. That's what DATA_GET_PTR does and on failure it raises a TypeError. It's mruby's version of TypedData_Get_Struct.

@thomthom
Copy link

I see - so DATA_GET_PTR is to be preferred over DATA_GET_PTR? Or are there reasons to use DATA_PTR? Performance?

@cremno
Copy link
Contributor

cremno commented Oct 13, 2015

Well, if Foo#initialize is called for the first time you can't check for a specific mrb_data_type because it hasn't been set yet (normally Foo.allocate would have done that). So if you know that the type of a mrb_value variable is MRB_TT_DATA and either the data object has the correct data type or you don't care about it then use the DATA_PTR macro.

  // mrb_type(self) == MRB_TT_DATA because of MRB_SET_INSTANCE_TT(klass, MRB_TT_DATA)
  mrb_int *data = DATA_PTR(self);  // DATA_TYPE(self) == NULL; can't and don't need to care
  if (data) foo_free(mrb, data);
  mrb_data_init(self, NULL, &foo_data_type);  // DATA_TYPE(self) = &foo_data_type
  mrb_int val;
  mrb_get_args(mrb, "i", &val);
  data = mrb_malloc(mrb, sizeof *data);
  *data = val;
  DATA_PTR(self) = data;  // don't need to care (correct data type)

If you're writing a method that expects a data object with a specific data type, then you need to use DATA_GET_PTR otherwise some other kind of object (array, string, etc.) may be treated as data object. You don't want that.

There's also DATA_CHECK_GET_PTR however it returns a null pointer instead of raising an error. A use case is the implementation of relational operators like == that shouldn't raise any errors.

Take a look at the source code of the mruby-time gem if you haven't yet. You really don't have to (fully) understand it but it's a decent example how the data API should be used. Maybe somebody else knows a better (simpler) example.

@thomthom
Copy link

Thank you very much. I think that going back to mruby-time gem now with this info will make it easier to understand. I will probably make my own "hello world" example for my own testing - I can post back that if you want.

matz pushed a commit that referenced this issue Dec 7, 2017
matz pushed a commit that referenced this issue Dec 7, 2017
OpenSolaris/Illumos defines a "sun" constant...
matz pushed a commit that referenced this issue Dec 7, 2017
Suppress compiler warning [-Wpointer-sign]
shuujii added a commit to shuujii/mruby that referenced this issue Feb 10, 2021
### Example

##### example.rb

```ruby
h = {}
(1..17).each{h[_1] = _1}
(1..16).each{h.delete(_1)}
h.rehash
```

##### ASAN report

```console
$ bin/mruby example.rb
==52587==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000006998 at pc 0x55a29cddf96b bp 0x7fff7b1b1720 sp 0x7fff7b1b1710
READ of size 4 at 0x602000006998 thread T0
    #0 0x55a29cddf96a in ib_it_next /mruby/src/hash.c:639
    mruby#1 0x55a29cde2ca2 in ht_rehash /mruby/src/hash.c:900
    mruby#2 0x55a29cde379f in h_rehash /mruby/src/hash.c:996
    mruby#3 0x55a29cde7f3d in mrb_hash_rehash /mruby/src/hash.c:1735
    mruby#4 0x55a29ce77b62 in mrb_vm_exec /mruby/src/vm.c:1451
    mruby#5 0x55a29ce5fa88 in mrb_vm_run /mruby/src/vm.c:981
    mruby#6 0x55a29ceb87e1 in mrb_top_run /mruby/src/vm.c:2874
    mruby#7 0x55a29cf36bdf in mrb_load_exec mrbgems/mruby-compiler/core/parse.y:6805
    mruby#8 0x55a29cf36f25 in mrb_load_detect_file_cxt mrbgems/mruby-compiler/core/parse.y:6848
    mruby#9 0x55a29cdba0a2 in main /mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:347
    mruby#10 0x7f24ef43b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    mruby#11 0x55a29cdb4a6d in _start (/mruby/bin/mruby+0x2a3a6d)

0x602000006998 is located 0 bytes to the right of 8-byte region [0x602000006990,0x602000006998)
allocated by thread T0 here:
    #0 0x7f24f01cfffe in __interceptor_realloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dffe)
    mruby#1 0x55a29ceb9440 in mrb_default_allocf /mruby/src/state.c:68
    mruby#2 0x55a29cdba747 in mrb_realloc_simple /mruby/src/gc.c:228
    mruby#3 0x55a29cdba928 in mrb_realloc /mruby/src/gc.c:242
    mruby#4 0x55a29cde12e5 in ht_init /mruby/src/hash.c:749
    mruby#5 0x55a29cde2b8e in ht_rehash /mruby/src/hash.c:897
    mruby#6 0x55a29cde379f in h_rehash /mruby/src/hash.c:996
    mruby#7 0x55a29cde7f3d in mrb_hash_rehash /mruby/src/hash.c:1735
    mruby#8 0x55a29ce77b62 in mrb_vm_exec /mruby/src/vm.c:1451
    mruby#9 0x55a29ce5fa88 in mrb_vm_run /mruby/src/vm.c:981
    mruby#10 0x55a29ceb87e1 in mrb_top_run /mruby/src/vm.c:2874
    mruby#11 0x55a29cf36bdf in mrb_load_exec mrbgems/mruby-compiler/core/parse.y:6805
    mruby#12 0x55a29cf36f25 in mrb_load_detect_file_cxt mrbgems/mruby-compiler/core/parse.y:6848
    mruby#13 0x55a29cdba0a2 in main /mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:347
    mruby#14 0x7f24ef43b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
```
matz pushed a commit that referenced this issue Dec 21, 2022
add conf.enable_test into build_config.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants