Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions _scripts/dump_ruby_c_functions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,20 @@ def generate_go_file(definition:, header_dir:)
go_function_lines << "func #{go_function_name}(#{go_function_args.join(", ")}) #{go_function_typeref} {"

call_c_method = "C.#{definition[:function_name]}("
casted_go_args = definition[:args].map do |c_arg|
"#{cast_to_cgo_type(c_arg[:type])}(#{c_arg[:name]})"

casted_go_args = []
definition[:args].each do |c_arg|
if c_arg[:type] == "char*"
go_function_lines << "#{c_arg[:name]}Char, #{c_arg[:name]}CharClean := string2Char(#{c_arg[:name]})"
go_function_lines << "defer #{c_arg[:name]}CharClean()"
go_function_lines << ""

casted_go_args << "#{c_arg[:name]}Char"
else
casted_go_args << "#{cast_to_cgo_type(c_arg[:type])}(#{c_arg[:name]})"
end
end

call_c_method << casted_go_args.join(", ")
call_c_method << ")"

Expand Down Expand Up @@ -184,8 +195,6 @@ def cast_to_cgo_type(typename)
return "C.ulong"
when "unsigned int"
return "C.uint"
when "char*"
return "string2Char"
when "VALUE*"
return "toCValueArray"
when /^VALUE\s*\(\*func\)\s*\(ANYARGS\)$/
Expand Down
5 changes: 4 additions & 1 deletion ruby-internal-error.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ import (
// [Go's format]: https://pkg.go.dev/fmt
func RbRaise(exc VALUE, format string, a ...interface{}) {
str := fmt.Sprintf(format, a...)
C.__rb_raise(C.VALUE(exc), string2Char(str))
strChar, strCharClean := string2Char(str)
defer strCharClean()

C.__rb_raise(C.VALUE(exc), strChar)
}
5 changes: 4 additions & 1 deletion ruby-internal-intern-class.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ import (
//
// void rb_define_singleton_method(VALUE obj, const char *mid, VALUE(*func)(ANYARGS), int arity)
func RbDefineSingletonMethod(obj VALUE, mid string, fun unsafe.Pointer, arity int) {
C.rb_define_singleton_method(C.VALUE(obj), string2Char(mid), toFunctionPointer(fun), C.int(arity))
midChar, midCharClean := string2Char(mid)
defer midCharClean()

C.rb_define_singleton_method(C.VALUE(obj), midChar, toFunctionPointer(fun), C.int(arity))
}
5 changes: 4 additions & 1 deletion ruby-internal-method.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ import "unsafe"
//
// void rb_define_method(VALUE klass, const char *mid, VALUE (*func)(ANYARGS), int arity)
func RbDefineMethod(klass VALUE, mid string, fun unsafe.Pointer, arity int) {
C.rb_define_method(C.VALUE(klass), string2Char(mid), toFunctionPointer(fun), C.int(arity))
midChar, midCharClean := string2Char(mid)
defer midCharClean()

C.rb_define_method(C.VALUE(klass), midChar, toFunctionPointer(fun), C.int(arity))
}
20 changes: 16 additions & 4 deletions ruby-internal-module.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import "C"
//
// VALUE rb_define_module(const char *name)
func RbDefineModule(name string) VALUE {
return VALUE(C.rb_define_module(string2Char(name)))
nameChar, nameCharClean := string2Char(name)
defer nameCharClean()

return VALUE(C.rb_define_module(nameChar))
}

// RbDefineClassUnder calls `rb_define_class_under` in C
Expand All @@ -22,7 +25,10 @@ func RbDefineModule(name string) VALUE {
//
// VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
func RbDefineClassUnder(outer VALUE, name string, super VALUE) VALUE {
return VALUE(C.rb_define_class_under(C.VALUE(outer), string2Char(name), C.VALUE(super)))
nameChar, nameCharClean := string2Char(name)
defer nameCharClean()

return VALUE(C.rb_define_class_under(C.VALUE(outer), nameChar, C.VALUE(super)))
}

// RbDefineModuleUnder calls `rb_define_module_under` in C
Expand All @@ -31,7 +37,10 @@ func RbDefineClassUnder(outer VALUE, name string, super VALUE) VALUE {
//
// VALUE rb_define_module_under(VALUE outer, const char *name)
func RbDefineModuleUnder(outer VALUE, name string) VALUE {
return VALUE(C.rb_define_module_under(C.VALUE(outer), string2Char(name)))
nameChar, nameCharClean := string2Char(name)
defer nameCharClean()

return VALUE(C.rb_define_module_under(C.VALUE(outer), nameChar))
}

// RbDefineClass calls `rb_define_class` in C
Expand All @@ -40,5 +49,8 @@ func RbDefineModuleUnder(outer VALUE, name string) VALUE {
//
// VALUE rb_define_class(const char *name, VALUE super)
func RbDefineClass(name string, super VALUE) VALUE {
return VALUE(C.rb_define_class(string2Char(name), C.VALUE(super)))
nameChar, nameChsrClean := string2Char(name)
defer nameChsrClean()

return VALUE(C.rb_define_class(nameChar, C.VALUE(super)))
}
5 changes: 4 additions & 1 deletion ruby-internal-symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ import "C"
//
// ID rb_intern(const char *name)
func RbIntern(name string) ID {
return ID(C.rb_intern(string2Char(name)))
nameChar, nameCharClean := string2Char(name)
defer nameCharClean()

return ID(C.rb_intern(nameChar))
}
45 changes: 35 additions & 10 deletions wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,41 @@ import (
)

// String2Char convert from Go string to [*Char]
func String2Char(str string) *Char {
return (*Char)(string2Char(str))
//
// 2nd return value is a function to free pointer.
// To prevent memory leaks, this function must always be called with defer when calling this function
//
// Example
//
// char, clean := ruby.String2Char("ABCD")
// defer clean()
func String2Char(str string) (*Char, func()) {
char, clean := string2Char(str)
return (*Char)(char), clean
}

// string2Char convert from Go string to `*C.char`. (for internal use within package)
func string2Char(str string) *C.char {
// FIXME: Go's string isn't a null-terminated string, so using `*(*[]byte)(unsafe.Pointer(&str))`
//
// 2nd return value is a function to free pointer.
// To prevent memory leaks, this function must always be called with defer when calling this function
//
// Example
//
// char, clean := string2Char("ABCD")
// defer clean()
func string2Char(str string) (*C.char, func()) {
// FIXME: https://www.slideshare.net/slideshow/ruby-meets-go/56074507#28
// recommends avoiding unnecessary copying of string.
// But current Go's string isn't a null-terminated string, so using `*(*[]byte)(unsafe.Pointer(&str))`
// will return a non-null-terminated byte array and can't be `*C.char`.
// Therefore, copying of byte arrays is unavoidable,
// so a slice is created that minimizes `len` and `cap` with null-terminations included
bytes := make([]byte, len(str)+1)
copy(bytes, str)
// Therefore, copying of byte arrays is unavoidable...
cstr := C.CString(str)

return (*C.char)(unsafe.Pointer(&bytes[0]))
clean := func() {
C.free(unsafe.Pointer(cstr))
}

return cstr, clean
}

// Value2String convert from [VALUE] to Go string
Expand Down Expand Up @@ -56,7 +77,11 @@ func string2Value(str string) C.VALUE {
if len(str) == 0 {
return rbUtf8StrNew(nil, C.long(0))
}
return rbUtf8StrNew(string2Char(str), stringLen(str))

strChar, strCharClean := string2Char(str)
defer strCharClean()

return rbUtf8StrNew(strChar, stringLen(str))
}

// toFunctionPointer returns a pointer to function. (for internal use within package)
Expand Down