diff --git a/vips/image_test.go b/vips/image_test.go index 44211e5d..71502920 100644 --- a/vips/image_test.go +++ b/vips/image_test.go @@ -1474,6 +1474,53 @@ func TestNewImageFromGoImage(t *testing.T) { assert.Equal(t, []float64{255, 0, 0, 255}, pixel) } +// TestGetPoint_RepeatedCalls verifies that GetPoint returns a Go-owned slice +// whose data remains valid across multiple calls. Previously, vipsGetPoint +// returned a slice backed by C memory and deferred a g_free on a nil pointer +// (captured before the C call), leaking the allocation. +func TestGetPoint_RepeatedCalls(t *testing.T) { + require.NoError(t, Startup(nil)) + + img, err := NewImageFromFile(resources + "png-24bit.png") + require.NoError(t, err) + defer img.Close() + + // Call GetPoint multiple times and retain results. + // If the returned slice were backed by C memory, earlier results could + // be corrupted by later calls. + var results [][]float64 + for i := 0; i < 10; i++ { + p, err := img.GetPoint(10, 10) + require.NoError(t, err) + results = append(results, p) + } + + // All results should be identical and independently owned. + for i, p := range results { + assert.Equal(t, 3, len(p), "iteration %d", i) + assert.Equal(t, 255.0, p[0], "iteration %d", i) + assert.Equal(t, 255.0, p[1], "iteration %d", i) + assert.Equal(t, 255.0, p[2], "iteration %d", i) + } +} + +// TestGetAsString_RoundTrip verifies that GetAsString correctly returns +// string metadata. Previously, vipsImageGetAsString deferred freeCString +// on a nil pointer (captured before the C call), leaking the allocated string. +func TestGetAsString_RoundTrip(t *testing.T) { + require.NoError(t, Startup(nil)) + + img, err := NewImageFromFile(resources + "jpg-24bit.jpg") + require.NoError(t, err) + defer img.Close() + + img.SetString("test-field", "hello-world") + + // GetAsString returns a formatted version of the field value. + result := img.GetAsString("test-field") + assert.Contains(t, result, "hello-world") +} + func TestNewImageFromGoImage_RoundTrip(t *testing.T) { require.NoError(t, Startup(nil)) diff --git a/vips/operations.go b/vips/operations.go index 0e60206f..f8c9bbe7 100644 --- a/vips/operations.go +++ b/vips/operations.go @@ -30,14 +30,16 @@ func vipsFindTrim(in *C.VipsImage, threshold float64, backgroundColor *Color) (i func vipsGetPoint(in *C.VipsImage, n int, x int, y int) ([]float64, error) { incOpCounter("getpoint") var out *C.double - defer gFreePointer(unsafe.Pointer(out)) if err := C.getpoint(in, &out, C.int(n), C.int(x), C.int(y)); err != 0 { return nil, handleVipsError() } - // maximum n is 4 - return (*[4]float64)(unsafe.Pointer(out))[:n:n], nil + // Copy from C memory into a Go slice, then free the C allocation. + result := make([]float64, n) + copy(result, (*[4]float64)(unsafe.Pointer(out))[:n:n]) + gFreePointer(unsafe.Pointer(out)) + return result, nil } // https://www.libvips.org/API/current/libvips-arithmetic.html#vips-min @@ -866,7 +868,7 @@ func vipsImageGetAsString(in *C.VipsImage, name string) string { cField := C.CString(name) defer freeCString(cField) var cFieldValue *C.char - defer freeCString(cFieldValue) + defer func() { freeCString(cFieldValue) }() if int(C.image_get_as_string(in, cField, &cFieldValue)) == 0 { return C.GoString(cFieldValue) }