[Libguestfs] [PATCH 4/4] Go bindings: fix "C array of strings" -- char** -- allocation

Laszlo Ersek
2021-09-20T05:23:35Z

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