Possible memory leak of the C-allocated `OpenCVResult.Message` in `contrib.OpenCVResult`
Possible memory leak of the C-allocated OpenCVResult.Message in contrib.OpenCVResult
I found a possible memory leak in contrib.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 contrib
package copies it with C.GoString and returns without calling C.free, so every failed
contrib operation leaks one heap allocation. This affects the ordinary error path of all
29 contrib 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: contrib/errors.go
Function: gocv.io/x/gocv/contrib.OpenCVResult
package contrib
/*
#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
contriboperation — e.g. thecontribwrappers (return OpenCVResult(C.SomeContribCall(...))). - The C++ wrapper runs the OpenCV call inside
try/catch; oncv::Exceptionit returnserrorResult(e.code, e.what()). errorResultmallocsstrlen(message)+1bytes, copies the message in, and stores the pointer in the returned-by-value struct fieldri.Message. cgo copies the struct into the Go frame, soresult.Messagebecomes the only reference to that heap block in the program.contrib/errors.go:14callsC.GoString(result.Message), which copies the bytes into a new Go string, then returns. There is nodefer, nounsafeimport, and noC.freeanywhere in the file;resultis 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
mallocblock ofstrlen(e.what())+1bytes is leaked per failed operation. OpenCV exception strings have the formOpenCV(<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.
_ = contrib.ColorChange(empty, empty, &dst, 1.0, 1.0, 1.0)
}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 an OpenCV build with the opencv_contrib modules (the default for gocv).
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.)
Source: hybridgroup/gocv