Skip to content

Possible memory leak of the C-allocated OpenCVResult.Message in cuda.OpenCVResult #1376

Description

@OvOhao

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))
}
  1. Go calls any wrapped cuda operation — e.g. cuda/arithm.go:25 (return OpenCVResult(C.GpuAbs(src.p, dst.p, nil))).
  2. The C++ wrapper runs the OpenCV call inside try/catch; on cv::Exception it returns
    errorResult(e.code, e.what()).
  3. 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.
  4. 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.
  5. 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.)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions