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

Daniel P. Berrangé berrange at redhat.com
Mon Sep 20 10:37:02 UTC 2021


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 :|




More information about the Libguestfs mailing list