Possible memory leak of the C-allocated OpenCVResult.Message in cuda.OpenCVResult
I found a possible memory leak in cuda.OpenCVResult. The C++ layer reports errors by
malloc-ing a copy of cv::Exception::what() and returning it inside an OpenCVResult struct.
The converter in the root package frees that pointer, but the identical converter in the cuda
package copies it with C.GoString and returns without calling C.free, so every failed
cuda operation leaks one heap allocation. This affects the ordinary error path of all
144 cuda call sites, so a long-running service that encounters recoverable OpenCV errors
(bad input frames, size or depth mismatches) accumulates leaked message buffers indefinitely.
File: cuda/errors.go
Function: gocv.io/x/gocv/cuda.OpenCVResult
package cuda
/*
#include "../core.h"
*/
import "C"
import "errors"
// Converts a OpenCVResult struct to an error.
func OpenCVResult(result C.OpenCVResult) error {
if result.Code == 0 {
return nil
}
return errors.New(C.GoString(result.Message))
}
The message is allocated in core.cpp:30-40:
OpenCVResult errorResult(int code, const char* message) {
OpenCVResult ri;
ri.Code = code;
auto res = (char*)malloc(strlen(message)+1);
memset(res, 0, strlen(message)+1);
memcpy(res, message, strlen(message));
ri.Message = res;
ri.Length = strlen(message);
return ri;
}
and the same function in the root package (core.go:42-52) does release it:
func OpenCVResult(result C.OpenCVResult) error {
if result.Code == 0 {
return nil
}
if result.Message == nil {
return errors.New("unknown openCV error")
}
defer C.free(unsafe.Pointer(result.Message))
return errors.New(C.GoString(result.Message))
}
- Go calls any wrapped
cuda operation — e.g. cuda/arithm.go:25 (return OpenCVResult(C.GpuAbs(src.p, dst.p, nil))).
- The C++ wrapper runs the OpenCV call inside
try/catch; on cv::Exception it returns
errorResult(e.code, e.what()).
errorResult mallocs strlen(message)+1 bytes, copies the message in, and stores the
pointer in the returned-by-value struct field ri.Message. cgo copies the struct into the Go
frame, so result.Message becomes the only reference to that heap block in the program.
cuda/errors.go:14 calls C.GoString(result.Message), which copies the bytes into a new Go
string, then returns. There is no defer, no unsafe import, and no C.free anywhere in
the file; result is a by-value parameter, so after the function returns no Go code holds the
pointer and no other function is able to free it.
- One
malloc block of strlen(e.what())+1 bytes is leaked per failed operation. OpenCV
exception strings have the form
OpenCV(<ver>) <abs-path>:<line>: error: (<code>:<name>) <detail> in function '<fn>',
typically 120–400 bytes and growing with the source path length.
Go trigger (if applicable):
for i := 0; i < 1_000_000; i++ {
// Any op that raises cv::Exception takes the errorResult path.
_ = cuda.Abs(src, &dst)
}
Any input that makes an OpenCV call throw works (empty matrix, mismatched sizes, unsupported
depth); no special privileges are needed. The leak is purely synchronous — no goroutine, callback
or GC step is involved, and Go's collector never reclaims malloc memory. It requires a CUDA-enabled build.
Suggested fix: mirror the root package's converter exactly — nil-check the message and release it
with defer:
import "unsafe"
func OpenCVResult(result C.OpenCVResult) error {
if result.Code == 0 {
return nil
}
if result.Message == nil {
return errors.New("unknown openCV error")
}
defer C.free(unsafe.Pointer(result.Message))
return errors.New(C.GoString(result.Message))
}
(The same omission exists in the sibling package's errors.go, reported separately.)
Possible memory leak of the C-allocated
OpenCVResult.Messageincuda.OpenCVResultI found a possible memory leak in
cuda.OpenCVResult. The C++ layer reports errors bymalloc-ing a copy ofcv::Exception::what()and returning it inside anOpenCVResultstruct.The converter in the root package frees that pointer, but the identical converter in the
cudapackage copies it with
C.GoStringand returns without callingC.free, so every failedcudaoperation leaks one heap allocation. This affects the ordinary error path of all144
cudacall sites, so a long-running service that encounters recoverable OpenCV errors(bad input frames, size or depth mismatches) accumulates leaked message buffers indefinitely.
File:
cuda/errors.goFunction:
gocv.io/x/gocv/cuda.OpenCVResultThe message is allocated in
core.cpp:30-40:and the same function in the root package (
core.go:42-52) does release it:cudaoperation — e.g.cuda/arithm.go:25(return OpenCVResult(C.GpuAbs(src.p, dst.p, nil))).try/catch; oncv::Exceptionit returnserrorResult(e.code, e.what()).errorResultmallocsstrlen(message)+1bytes, copies the message in, and stores thepointer in the returned-by-value struct field
ri.Message. cgo copies the struct into the Goframe, so
result.Messagebecomes the only reference to that heap block in the program.cuda/errors.go:14callsC.GoString(result.Message), which copies the bytes into a new Gostring, then returns. There is no
defer, nounsafeimport, and noC.freeanywhere inthe file;
resultis a by-value parameter, so after the function returns no Go code holds thepointer and no other function is able to free it.
mallocblock ofstrlen(e.what())+1bytes is leaked per failed operation. OpenCVexception strings have the form
OpenCV(<ver>) <abs-path>:<line>: error: (<code>:<name>) <detail> in function '<fn>',typically 120–400 bytes and growing with the source path length.
Go trigger (if applicable):
Any input that makes an OpenCV call throw works (empty matrix, mismatched sizes, unsupported
depth); no special privileges are needed. The leak is purely synchronous — no goroutine, callback
or GC step is involved, and Go's collector never reclaims
mallocmemory. It requires a CUDA-enabled build.Suggested fix: mirror the root package's converter exactly — nil-check the message and release it
with
defer:(The same omission exists in the sibling package's
errors.go, reported separately.)