Laszlo Ersek
2021-Sep-20 05:23 UTC
[Libguestfs] [PATCH 4/4] Go bindings: fix "C array of strings" -- char** -- allocation
The current "arg_string_list" and "free_string_list"
implementations go
back to commit b6f01f32606d ("Add Go (language) bindings.",
2013-07-03).
There are two problems with them:
- "free_string_list" doesn't actually free anything,
- at the *first* such g.Internal_test() call site that passes an
Ostringlist member inside the Optargs argument, namely:
> g.Internal_test ("abc",
> string_addr ("def"),
> []string{},
> false,
> 0,
> 0,
> "123",
> "456",
> []byte{'a', 'b', 'c',
'\000', 'a', 'b', 'c'},
> &guestfs.OptargsInternal_test{Ostringlist_is_set:
true,
> Ostringlist: []string{}
> }
> )
the "golang/run-bindtests" case crashes:
> panic: runtime error: cgo argument has Go pointer to Go pointer
>
> goroutine 1 [running]:
> libguestfs.org/guestfs.(*Guestfs).Internal_test.func7(0xc000018180,
> 0xadfb60, 0xadfb80, 0xc000010048, 0x0, 0x0, 0x0, 0xae3e10, 0xae3e30,
> 0xade3a0, ...)
> golang/src/libguestfs.org/guestfs/guestfs.go:6729 +0xa9
> libguestfs.org/guestfs.(*Guestfs).Internal_test(0xc000018180, 0x4ee3a5,
> 0x3, 0xc000061be8, 0xc000061af8, 0x0, 0x0, 0xc000061a00, 0x0, 0x0, ...)
> golang/src/libguestfs.org/guestfs/guestfs.go:6729 +0x3c9
> main.main()
> golang/bindtests/bindtests.go:77 +0x151e
> exit status 2
> FAIL run-bindtests (exit status: 1)
It turns out that a C-language array containing C-language pointers can
only be allocated in C:
https://stackoverflow.com/questions/35924545/golang-cgo-panic-runtime-error-cgo-argument-has-go-pointer-to-go-pointer
Rewrite the "arg_string_list" and "free_string_list"
functions almost
entirely in C.
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
generator/golang.ml | 46 ++++++++++++++++++++++++++++++++++++---------
1 file changed, 37 insertions(+), 9 deletions(-)
diff --git a/generator/golang.ml b/generator/golang.ml
index d328abe4ed08..5db478e8a494 100644
--- a/generator/golang.ml
+++ b/generator/golang.ml
@@ -52,6 +52,39 @@ _go_guestfs_create_flags (unsigned flags)
{
return guestfs_create_flags (flags);
}
+
+//
+// A C-language array of C-language pointers needs to be allocated in C.
+//
https://stackoverflow.com/questions/35924545/golang-cgo-panic-runtime-error-cgo-argument-has-go-pointer-to-go-pointer
+//
+typedef char *pChar;
+
+pChar *allocStringArray (size_t nmemb)
+{
+ pChar *array;
+
+ array = malloc (sizeof (pChar) * (nmemb + 1));
+ array[nmemb] = NULL;
+ return array;
+}
+
+void storeString (pChar *array, size_t idx, pChar string)
+{
+ array[idx] = string;
+}
+
+void freeStringArray (pChar *array)
+{
+ pChar *position;
+ pChar string;
+
+ position = array;
+ while ((string = *position) != NULL) {
+ free (string);
+ position++;
+ }
+ free (array);
+}
*/
import \"C\"
@@ -148,12 +181,11 @@ func (g *Guestfs) Close () *GuestfsError {
* C strings and golang []string.
*/
func arg_string_list (xs []string) **C.char {
- r := make ([]*C.char, 1 + len (xs))
+ r := C.allocStringArray (C.size_t (len (xs)))
for i, x := range xs {
- r[i] = C.CString (x)
+ C.storeString (r, C.size_t (i), C.CString (x));
}
- r[len (xs)] = nil
- return &r[0]
+ return (**C.char) (r)
}
func count_string_list (argv **C.char) int {
@@ -167,11 +199,7 @@ func count_string_list (argv **C.char) int {
}
func free_string_list (argv **C.char) {
- for *argv != nil {
- //C.free (*argv)
- argv = (**C.char) (unsafe.Pointer (uintptr (unsafe.Pointer (argv)) +
- unsafe.Sizeof (*argv)))
- }
+ C.freeStringArray ((*C.pChar) (argv))
}
func return_string_list (argv **C.char) []string {
--
2.19.1.3.g30247aa5d201
Richard W.M. Jones
2021-Sep-20 10:36 UTC
[Libguestfs] [PATCH 4/4] Go bindings: fix "C array of strings" -- char** -- allocation
On Mon, Sep 20, 2021 at 07:23:35AM +0200, Laszlo Ersek wrote:> The current "arg_string_list" and "free_string_list" implementations go > back to commit b6f01f32606d ("Add Go (language) bindings.", 2013-07-03). > There are two problems with them: > > - "free_string_list" doesn't actually free anything, > > - at the *first* such g.Internal_test() call site that passes an > Ostringlist member inside the Optargs argument, namely: > > > g.Internal_test ("abc", > > string_addr ("def"), > > []string{}, > > false, > > 0, > > 0, > > "123", > > "456", > > []byte{'a', 'b', 'c', '\000', 'a', 'b', 'c'}, > > &guestfs.OptargsInternal_test{Ostringlist_is_set: true, > > Ostringlist: []string{} > > } > > ) > > the "golang/run-bindtests" case crashes: > > > panic: runtime error: cgo argument has Go pointer to Go pointer > > > > goroutine 1 [running]: > > libguestfs.org/guestfs.(*Guestfs).Internal_test.func7(0xc000018180, > > 0xadfb60, 0xadfb80, 0xc000010048, 0x0, 0x0, 0x0, 0xae3e10, 0xae3e30, > > 0xade3a0, ...) > > golang/src/libguestfs.org/guestfs/guestfs.go:6729 +0xa9 > > libguestfs.org/guestfs.(*Guestfs).Internal_test(0xc000018180, 0x4ee3a5, > > 0x3, 0xc000061be8, 0xc000061af8, 0x0, 0x0, 0xc000061a00, 0x0, 0x0, ...) > > golang/src/libguestfs.org/guestfs/guestfs.go:6729 +0x3c9 > > main.main() > > golang/bindtests/bindtests.go:77 +0x151e > > exit status 2 > > FAIL run-bindtests (exit status: 1) > > It turns out that a C-language array containing C-language pointers can > only be allocated in C: > > https://stackoverflow.com/questions/35924545/golang-cgo-panic-runtime-error-cgo-argument-has-go-pointer-to-go-pointer > > Rewrite the "arg_string_list" and "free_string_list" functions almost > entirely in C. > > Signed-off-by: Laszlo Ersek <lersek at redhat.com> > --- > generator/golang.ml | 46 ++++++++++++++++++++++++++++++++++++--------- > 1 file changed, 37 insertions(+), 9 deletions(-) > > diff --git a/generator/golang.ml b/generator/golang.ml > index d328abe4ed08..5db478e8a494 100644 > --- a/generator/golang.ml > +++ b/generator/golang.ml > @@ -52,6 +52,39 @@ _go_guestfs_create_flags (unsigned flags) > { > return guestfs_create_flags (flags); > } > + > +// > +// A C-language array of C-language pointers needs to be allocated in C. > +// https://stackoverflow.com/questions/35924545/golang-cgo-panic-runtime-error-cgo-argument-has-go-pointer-to-go-pointer > +// > +typedef char *pChar; > + > +pChar *allocStringArray (size_t nmemb) > +{ > + pChar *array; > + > + array = malloc (sizeof (pChar) * (nmemb + 1)); > + array[nmemb] = NULL; > + return array; > +} > + > +void storeString (pChar *array, size_t idx, pChar string) > +{ > + array[idx] = string; > +} > + > +void freeStringArray (pChar *array) > +{ > + pChar *position; > + pChar string; > + > + position = array; > + while ((string = *position) != NULL) { > + free (string); > + position++; > + } > + free (array); > +} > */ > import \"C\" > > @@ -148,12 +181,11 @@ func (g *Guestfs) Close () *GuestfsError { > * C strings and golang []string. > */ > func arg_string_list (xs []string) **C.char { > - r := make ([]*C.char, 1 + len (xs)) > + r := C.allocStringArray (C.size_t (len (xs))) > for i, x := range xs { > - r[i] = C.CString (x) > + C.storeString (r, C.size_t (i), C.CString (x)); > } > - r[len (xs)] = nil > - return &r[0] > + return (**C.char) (r) > } > > func count_string_list (argv **C.char) int { > @@ -167,11 +199,7 @@ func count_string_list (argv **C.char) int { > } > > func free_string_list (argv **C.char) { > - for *argv != nil { > - //C.free (*argv) > - argv = (**C.char) (unsafe.Pointer (uintptr (unsafe.Pointer (argv)) + > - unsafe.Sizeof (*argv))) > - } > + C.freeStringArray ((*C.pChar) (argv)) > } > > func return_string_list (argv **C.char) []string {I had to do something similar to this in libnbd recently. Thanks, ACK series. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into KVM guests. http://libguestfs.org/virt-v2v
Daniel P. Berrangé
2021-Sep-20 10:37 UTC
[Libguestfs] [PATCH 4/4] Go bindings: fix "C array of strings" -- char** -- allocation
On Mon, Sep 20, 2021 at 07:23:35AM +0200, Laszlo Ersek wrote:> The current "arg_string_list" and "free_string_list" implementations go > back to commit b6f01f32606d ("Add Go (language) bindings.", 2013-07-03). > There are two problems with them: > > - "free_string_list" doesn't actually free anything,Doh.> > - at the *first* such g.Internal_test() call site that passes an > Ostringlist member inside the Optargs argument, namely: > > > g.Internal_test ("abc", > > string_addr ("def"), > > []string{}, > > false, > > 0, > > 0, > > "123", > > "456", > > []byte{'a', 'b', 'c', '\000', 'a', 'b', 'c'}, > > &guestfs.OptargsInternal_test{Ostringlist_is_set: true, > > Ostringlist: []string{} > > } > > ) > > the "golang/run-bindtests" case crashes: > > > panic: runtime error: cgo argument has Go pointer to Go pointer > > > > goroutine 1 [running]: > > libguestfs.org/guestfs.(*Guestfs).Internal_test.func7(0xc000018180, > > 0xadfb60, 0xadfb80, 0xc000010048, 0x0, 0x0, 0x0, 0xae3e10, 0xae3e30, > > 0xade3a0, ...) > > golang/src/libguestfs.org/guestfs/guestfs.go:6729 +0xa9 > > libguestfs.org/guestfs.(*Guestfs).Internal_test(0xc000018180, 0x4ee3a5, > > 0x3, 0xc000061be8, 0xc000061af8, 0x0, 0x0, 0xc000061a00, 0x0, 0x0, ...) > > golang/src/libguestfs.org/guestfs/guestfs.go:6729 +0x3c9 > > main.main() > > golang/bindtests/bindtests.go:77 +0x151e > > exit status 2 > > FAIL run-bindtests (exit status: 1)What distro / go version do you see this on, as I can't reproduce this pointer problem with a standalone demo app ?> > It turns out that a C-language array containing C-language pointers can > only be allocated in C: > > https://stackoverflow.com/questions/35924545/golang-cgo-panic-runtime-error-cgo-argument-has-go-pointer-to-go-pointer > > Rewrite the "arg_string_list" and "free_string_list" functions almost > entirely in C. > > Signed-off-by: Laszlo Ersek <lersek at redhat.com> > --- > generator/golang.ml | 46 ++++++++++++++++++++++++++++++++++++--------- > 1 file changed, 37 insertions(+), 9 deletions(-) > > diff --git a/generator/golang.ml b/generator/golang.ml > index d328abe4ed08..5db478e8a494 100644 > --- a/generator/golang.ml > +++ b/generator/golang.ml > @@ -52,6 +52,39 @@ _go_guestfs_create_flags (unsigned flags) > { > return guestfs_create_flags (flags); > } > + > +// > +// A C-language array of C-language pointers needs to be allocated in C. > +// https://stackoverflow.com/questions/35924545/golang-cgo-panic-runtime-error-cgo-argument-has-go-pointer-to-go-pointer > +// > +typedef char *pChar; > + > +pChar *allocStringArray (size_t nmemb) > +{ > + pChar *array; > + > + array = malloc (sizeof (pChar) * (nmemb + 1)); > + array[nmemb] = NULL; > + return array; > +} > + > +void storeString (pChar *array, size_t idx, pChar string) > +{ > + array[idx] = string; > +} > + > +void freeStringArray (pChar *array) > +{ > + pChar *position; > + pChar string; > + > + position = array; > + while ((string = *position) != NULL) { > + free (string); > + position++; > + } > + free (array); > +} > */ > import \"C\" > > @@ -148,12 +181,11 @@ func (g *Guestfs) Close () *GuestfsError { > * C strings and golang []string. > */ > func arg_string_list (xs []string) **C.char { > - r := make ([]*C.char, 1 + len (xs)) > + r := C.allocStringArray (C.size_t (len (xs))) > for i, x := range xs { > - r[i] = C.CString (x) > + C.storeString (r, C.size_t (i), C.CString (x)); > } > - r[len (xs)] = nil > - return &r[0] > + return (**C.char) (r) > }This could be done purely in Go without helper functions func array_elem(arr **C.char, idx int) **C.char { return (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(arr)) + (unsafe.Sizeof(arr) * uintptr(idx)))) } func array_size(arr **C.char, len int) C.size_t { return C.size_t(unsafe.Sizeof(*arr) * (1 + uintptr(len))) } func arg_string_list2(xs []string) **C.char { var r **C.char r = (**C.char)(C.malloc(array_size(len(xs)))) for i, x := range xs { str := array_elem(r, i) *str = C.CString(x) } str := array_elem(r, len(xs)) *str = nil return r }> > func count_string_list (argv **C.char) int { > @@ -167,11 +199,7 @@ func count_string_list (argv **C.char) int { > } > > func free_string_list (argv **C.char) { > - for *argv != nil { > - //C.free (*argv) > - argv = (**C.char) (unsafe.Pointer (uintptr (unsafe.Pointer (argv)) + > - unsafe.Sizeof (*argv))) > - } > + C.freeStringArray ((*C.pChar) (argv)) > }This can be done entirely in Go func free_string_list (argv **C.char) { for i := 0; ; i++ { str := array_elem(argv, i) if *str == nil { break } C.free(unsafe.Pointer(*str)) } C.free(unsafe.Pointer(argv)) }> > func return_string_list (argv **C.char) []string { > -- > 2.19.1.3.g30247aa5d201 > > _______________________________________________ > Libguestfs mailing list > Libguestfs at redhat.com > https://listman.redhat.com/mailman/listinfo/libguestfs >Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|